• 深入分析 Java对象进入老年代的四种方式


    对象的内存分配,就是在堆上分配(也可能经过 JIT 编译后被拆散为标量类型并间接在栈上分配),对象主要分配在新生代的 Eden 区上,少数情况下可能直接分配在老年代,分配规则不固定,取决于当前使用的垃圾收集器组合以及相关的参数配置

    对象优先在 Eden 分配

    大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。

    👇Minor GC vs Major GC/Full GC

    • Minor GC:回收新生代(包括 Eden 和 Survivor 区域),因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
    • Major GC / Full GC:回收老年代,出现了 Major GC,经常会伴随至少一次的 Minor GC,但这并非绝对。Major GC 的速度一般会比 Minor GC 慢 10 倍 以上。

    在 JVM 规范中,Major GC 和 Full GC 都没有一个正式的定义,所以有人也简单地认为 Major GC 清理老年代,而 Full GC 清理整个内存堆。

    进入老年代的情况:

    • minor gc后,survivor区空间不能容纳全部存活对象
    • 存活对象达到年龄阈值。比如15
    • 大对象
    • 动态年龄判断:是年龄从小到大的对象占据的空间,大于survivor区域的一半,然后把等于或大于该年龄的对象,放入到老年代

    大对象:所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是很长的字符串及数组,比遇到一个大对象更加坏的消息就是遇到一群“朝生昔死”的短命大对象,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾回收以获取足够的连续空间来安置他们

    -XX:PretenureSizeThreshold=3m 新生代中大于等于3m的对象,就是大对象

    -XX:NewSize=10m -XX:MaxNewSize=10m -XX:InitialHeapSize=20m

    这里,我们给新生代10m,堆20m。

    也就是说,老年代的空间=20m-10m=10m

    -XX:SurvivorRatio=8

    eden:s0:s1等于8:1:1

    所以eden区为8m,s0为1m,s1为1m

    使用的垃圾收集器为ParNew和CMS

    -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

    打印GC的详细信息

    -XX:+PrintGCDetails

    打印GC的时间戳

    -XX:+PrintGCTimeStamps

    那这些GC日志输出在哪里,当然是文件啦。

    -Xloggc:bigobject.log

    jvm参数:-XX:NewSize=10m -XX:MaxNewSize=10m -XX:InitialHeapSize=20m -XX:MaxHeapSize=20m -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3m -XX:MaxTenuringThreshold=15 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:bigobject.log
    
    • 1

    1、minor gc之后,survivor区空间不能容量存活对象

    public class mode_1 {
    
      private static final int _1MB = 1024 * 1024;
    
      public static void main(String[] args) {
        byte[] array1 = new byte[2*_1MB];
        array1 = new byte[2*_1MB];
        array1 = new byte[2*_1MB];
    
        byte[] array2 = new byte[128*1024];
        array2 = null;
    
        byte[] array3 = new byte[2*_1MB];//这里触发第一次minor gc
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    jvm参数

    -verbose:gc
    -XX:NewSize=10m
    -XX:MaxNewSize=10m
    -XX:InitialHeapSize=20m
    -XX:MaxHeapSize=20m
    -XX:SurvivorRatio=8
    -XX:PretenureSizeThreshold=10m
    -XX:MaxTenuringThreshold=15
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -Xloggc:survivor_live.log
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    gc、heap 日志

    OpenJDK 64-Bit Server VM (25.345-b01) for bsd-aarch64 JRE (Zulu 8.64.0.19-CA-macos-aarch64) (1.8.0_345-b01), built on Aug  2 2022 02:17:56 by "zulu_re" with gcc Apple LLVM 12.0.0 (clang-1200.0.32.28)
    Memory: 16k page, physical 16777216k(428352k free)
    
    /proc/meminfo:
    0.055: [GC (Allocation Failure) 0.055: [ParNew: 8144K->488K(9216K), 0.0006237 secs] 8144K->2538K(23552K), 0.0007117 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap
     par new generation   total 9216K, used 2989K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 8192K,  30% used [0x00000007be800000, 0x00000007bea712c0, 0x00000007bf000000)
      from space 1024K,  47% used [0x00000007bf100000, 0x00000007bf17a318, 0x00000007bf200000)
      to   space 1024K,   0% used [0x00000007bf000000, 0x00000007bf000000, 0x00000007bf100000)
     concurrent mark-sweep generation total 14336K, used 2050K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3222K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 345K, capacity 388K, committed 512K, reserved 1048576K
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以发现在发生minor gc前,加入了3个2M,一个128k在eden里边,eden大小为8M,再加入2M就发送了minor gc,可以看到新生代内存从8144K减少到488K,整个堆内存占用从8144K减少到2538K,array1不引用的两个2M都被垃圾回收掉,新生代剩下488K(系统原本有的)放入的幸存区from,但是array1最后一个引用2M大于幸存区from(1M)直接进入老年代

    发现老年代中占用2050K,正是最后一个array1引用的byte数组,在垃圾回收结束后,array3放入eden,幸存区放入448K

    2、对象达到年龄阈值进入老年代

    JVM 给每个对象定义了一个对象年龄计数器。当新生代发生一次 Minor GC 后,存活下来的对象年龄 +1,当年龄超过一定值时,就将超过该值的所有对象转移到老年代中去。

    使用 -XXMaxTenuringThreshold 设置新生代的最大年龄,只要超过该参数的新生代对象都会被转移到老年代中去。

    public class mode_2 {
      public static void main(String[] args) {
        _2_year();
      }
      public static void _2_year() {
    
        int _1MB = 1024 * 1024;
    
        byte[] array1 = new byte[2 * _1MB];
        array1 = new byte[2 * _1MB];
        array1 = new byte[1 * _1MB];
    
        byte[] array2 = new byte[128 * 1024];//经过三次GC后,想让这个128K进入老年代
    
        array1 = null;
    
        System.out.println("-----------1-----------");
        byte[] array3 = new byte[2 * _1MB];//第一次minor gc,此时array2指向的对象0岁
        array3 = new byte[2 * _1MB];
        array3 = new byte[2 * _1MB];
    
        array3 = null;
    
        System.out.println("-----------2-----------");
        byte[] array4 = new byte[2 * _1MB];//第二次minor gc,此时array2指向的对象1岁
        array4 = new byte[2 * _1MB];
        array4 = new byte[2 * _1MB];
        array4 = null;
    
        System.out.println("-----------3-----------");
        byte[] array5 = new byte[2 * _1MB];//第三次minor gc,此时array2指向的对象2岁
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    jvm参数

    -XX:NewSize=10m
    -XX:MaxNewSize=10m
    -XX:InitialHeapSize=20m
    -XX:MaxHeapSize=20m
    -XX:SurvivorRatio=6
    -XX:PretenureSizeThreshold=10m
    -XX:MaxTenuringThreshold=3  # 存活对象年龄超过3,则直接进入老年代
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -XX:+PrintTenuringDistribution  # 打印每次gc后幸存区内存大小、对象年龄阈值、存活对象大小、存活对象年龄
    -XX:+PrintHeapAtGC # 每次gc前后打印堆内存占用情况
    -XX:+PrintGCApplicationStoppedTime # 每次gc的stop the world时间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打印日志,缩略版

    -----------1-----------
    0.056: [GC (Allocation Failure) 0.056: [ParNew
    Desired survivor size 655360 bytes, new threshold 2 (max 2)
    - age   1:     602600 bytes,     602600 total
    : 7160K->621K(8960K), 0.0005195 secs] 7160K->621K(23296K), 0.0005389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    -----------2-----------
    0.057: [GC (Allocation Failure) 0.057: [ParNew
    Desired survivor size 655360 bytes, new threshold 2 (max 2)
    - age   1:        328 bytes,        328 total
    - age   2:     545408 bytes,     545736 total
    : 6992K->717K(8960K), 0.0004029 secs] 6992K->717K(23296K), 0.0004103 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    -----------3-----------
    0.058: [GC (Allocation Failure) 0.058: [ParNew
    Desired survivor size 655360 bytes, new threshold 2 (max 2)
    - age   1:        192 bytes,        192 total
    - age   2:        328 bytes,        520 total
    : 7046K->161K(8960K), 0.0013712 secs] 7046K->710K(23296K), 0.0013797 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    
    Heap
     par new generation   total 8960K, used 2458K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,  29% used [0x00000007be800000, 0x00000007bea3e4b0, 0x00000007bef80000)
      from space 1280K,  12% used [0x00000007bf0c0000, 0x00000007bf0e85e0, 0x00000007bf200000)
      to   space 1280K,   0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf0c0000)
     concurrent mark-sweep generation total 14336K, used 548K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3231K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 345K, capacity 388K, committed 512K, reserved 1048576K
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    分析gc heap打印情况

    根据我们设置启动的jvm参数,新生代为10M(eden7.5M,from 1.25M, to 1.25M),对象存活年龄阈值为2,Desired survivor size 524288bytes,当前

    1、首先创建了3个2M,1个128K

    • 当前堆内存占用情况(eden 7160K),之后eden再加入2M发生第一次minor gc
    • 此次minor gc 从7160K(2个2M,1个1M,1个128K,系统自带)减少到621K(128K+系统自带),系统自带的也会被部分垃圾回收,array1=null,所以被引用过的三个2M都被垃圾回收掉,array2被保留下来,可以看到此时幸存区剩下602600bytes,并且存活年龄为1
    • 完成本次gc之后,新生代剩下614K左右在幸存区中(可能与PrintTenuringDistribution打印的age分布有差异),eden剩下621K
    -----------1-----------
    {Heap before GC invocations=0 (full 0):
     par new generation   total 8960K, used 7160K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,  93% used [0x00000007be800000, 0x00000007beefe0c0, 0x00000007bef80000)
      from space 1280K,   0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf0c0000)
      to   space 1280K,   0% used [0x00000007bf0c0000, 0x00000007bf0c0000, 0x00000007bf200000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3200K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    0.056: [GC (Allocation Failure) 0.056: [ParNew
    Desired survivor size 655360 bytes, new threshold 2 (max 2)
    - age   1:     602600 bytes,     602600 total
    : # 7160K->621K(8960K), 0.0005195 secs] 7160K->621K(23296K), 0.0005389 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap after GC invocations=1 (full 0):
     par new generation   total 8960K, used 621K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,   0% used [0x00000007be800000, 0x00000007be800000, 0x00000007bef80000)
      from space 1280K,  48% used [0x00000007bf0c0000, 0x00000007bf15b728, 0x00000007bf200000)
      to   space 1280K,   0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf0c0000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3200K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 342K, capacity 388K, committed 512K, reserved 1048576K
    }
    0.057: Total time for which application threads were stopped: 0.0006402 seconds, Stopping threads took: 0.0000152 seconds
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2、新添加3个2M

    • 在第一次gc之后,在加入array3的3个2MB,观察内存占用情况,此时eden占用82%,其他的依旧没有什么变化;
    • 在添加array4引用的第一个2M,发生第二次minor gc,新生代的内存从6992K减少到717K,整个堆占用717K;
    • 很明显array3引用过的3个2M都被gc回收了,只有系统的112K,上一存活的602600bytes部分被垃圾回收之后剩下545408bytes,age为2;并且新增加了328bytes ,age为1;当前幸存区共占有545736bytes
    • gc后from占用717K,注意所有age分段占用的内存并不等于from区所占有的内存
    -----------2-----------
    {Heap before GC invocations=1 (full 0):
     par new generation   total 8960K, used 6992K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,  82% used [0x00000007be800000, 0x00000007bee38c80, 0x00000007bef80000)
      from space 1280K,  48% used [0x00000007bf0c0000, 0x00000007bf15b728, 0x00000007bf200000)
      to   space 1280K,   0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf0c0000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3215K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 343K, capacity 388K, committed 512K, reserved 1048576K
    0.057: [GC (Allocation Failure) 0.057: [ParNew
    Desired survivor size 655360 bytes, new threshold 2 (max 2)
    - age   1:        328 bytes,        328 total
    - age   2:     545408 bytes,     545736 total
    : # 6992K->717K(8960K), 0.0004029 secs] 6992K->717K(23296K), 0.0004103 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    Heap after GC invocations=2 (full 0):
     par new generation   total 8960K, used 717K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,   0% used [0x00000007be800000, 0x00000007be800000, 0x00000007bef80000)
      from space 1280K,  56% used [0x00000007bef80000, 0x00000007bf0337f8, 0x00000007bf0c0000)
      to   space 1280K,   0% used [0x00000007bf0c0000, 0x00000007bf0c0000, 0x00000007bf200000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3215K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 343K, capacity 388K, committed 512K, reserved 1048576K
    }
    0.058: Total time for which application threads were stopped: 0.0004802 seconds, Stopping threads took: 0.0000255 seconds
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3、新增3个2M

    • 新增3个2M之后,新生代内存占用了7046K,其他的依旧没有什么变化
    • 再新增2M之后,触发了第三次minor gc,新生代内存从7046K减少到161K,而整个堆内存的占用从7046K减少到了710K,其中161K进入了幸存区from,有548K左右进入了老年代
    • 为什么进入老年代,就是该对象的存活年龄已经超过2了,直接进入了老年代
    • 可以观察到幸存区的存活年龄中有新增了age 为1,可以明显发现,每次gc之后除了我们手动创建并且持续引用的对象不会被垃圾回收,系统运行所占用的内存空间也会部分会存活,但是他们在每次minor gc存活的大小是不可控的
    -----------3-----------
    {Heap before GC invocations=2 (full 0):
     par new generation   total 8960K, used 7046K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,  82% used [0x00000007be800000, 0x00000007bee2e0b8, 0x00000007bef80000)
      from space 1280K,  56% used [0x00000007bef80000, 0x00000007bf0337f8, 0x00000007bf0c0000)
      to   space 1280K,   0% used [0x00000007bf0c0000, 0x00000007bf0c0000, 0x00000007bf200000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3215K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 343K, capacity 388K, committed 512K, reserved 1048576K
    0.058: [GC (Allocation Failure) 0.058: [ParNew
    Desired survivor size 655360 bytes, new threshold 2 (max 2)
    - age   1:        192 bytes,        192 total
    - age   2:        328 bytes,        520 total
    : # 7046K->161K(8960K), 0.0013712 secs] 7046K->710K(23296K), 0.0013797 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap after GC invocations=3 (full 0):
     par new generation   total 8960K, used 161K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,   0% used [0x00000007be800000, 0x00000007be800000, 0x00000007bef80000)
      from space 1280K,  12% used [0x00000007bf0c0000, 0x00000007bf0e85e0, 0x00000007bf200000)
      to   space 1280K,   0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf0c0000)
     concurrent mark-sweep generation total 14336K, used 548K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3215K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 343K, capacity 388K, committed 512K, reserved 1048576K
    }
    0.059: Total time for which application threads were stopped: 0.0014371 seconds, Stopping threads took: 0.0000038 seconds
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4、最后新增2M

    观察到eden空间充足,直接添加2M不会引起gc

    Heap
     par new generation   total 8960K, used 2458K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 7680K,  29% used [0x00000007be800000, 0x00000007bea3e4b0, 0x00000007bef80000)
      from space 1280K,  12% used [0x00000007bf0c0000, 0x00000007bf0e85e0, 0x00000007bf200000)
      to   space 1280K,   0% used [0x00000007bef80000, 0x00000007bef80000, 0x00000007bf0c0000)
     concurrent mark-sweep generation total 14336K, used 548K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3231K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 345K, capacity 388K, committed 512K, reserved 1048576K
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、大对象进入老年代

    大对象是指需要大量连续内存空间的 Java 对象,如很长的字符串或数据。

    一个大对象能够存入 Eden 区的概率比较小,发生分配担保的概率比较大,而分配担保需要涉及大量的复制,就会造成效率低下。

    虚拟机提供了一个 -XX:PretenureSizeThreshold 参数,令大于等于这个设置值的对象直接在老年代分配,这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制。(还记得吗,新生代采用复制算法回收垃圾)

    public class mode_3 {
      private static final int _1MB = 1024 * 1024;
      public static void main(String[] args) throws InterruptedException {
        byte[] array1 = new byte[2*_1MB];
        byte[] array2 = new byte[3*_1MB];
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    jvm参数

    -XX:NewSize=10m
    -XX:MaxNewSize=10m
    -XX:InitialHeapSize=20m
    -XX:MaxHeapSize=20m
    -XX:SurvivorRatio=8
    -XX:PretenureSizeThreshold=3m  # 加入eden中的对象大于3M判定为大对象直接加入老年代
    -XX:MaxTenuringThreshold=15
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -XX:+PrintTenuringDistribution
    -XX:+PrintHeapAtGC
    -XX:+PrintGCApplicationStoppedTime
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打印heap内存信息

    可以发现现在新生代占用了4212K,包括我们自己创建的2M,剩下的都是系统启动自带的,还有3M被放入了老年代,这样验证了放入eden的对象大于等于3M就会被判定为大对象直接进入老年代(-XX:PretenureSizeThreshold=3m )

    Heap
     par new generation   total 9216K, used 4212K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 8192K,  51% used [0x00000007be800000, 0x00000007bec1d1f8, 0x00000007bf000000)
      from space 1024K,   0% used [0x00000007bf000000, 0x00000007bf000000, 0x00000007bf100000)
      to   space 1024K,   0% used [0x00000007bf100000, 0x00000007bf100000, 0x00000007bf200000)
     concurrent mark-sweep generation total 14336K, used 3072K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3229K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 345K, capacity 388K, committed 512K, reserved 1048576K
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4、动态年龄判断

    如果当前新生代的 Survivor 中,相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄 >= 该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄

    先入为主,书本中是这样描述动态年龄判断的概念的

    为了更好地适应不同程序的内存状态,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
    
    • 1

    但是其实这种说法是不对的,以下代码会验证这种情况发生并且是如何解决的

    public class mode_4 {
    
      private static final int _1MB = 1024 * 1024;
    
      public static void main(String[] args) {
    
        byte[] array1 = new byte[2 * _1MB];
        array1 = new byte[2 * _1MB];
        array1 = new byte[1 * _1MB];
        array1 = null;
    
        byte[] array2 = new byte[300 * 1024];
    
        System.out.println("------------1------------");
        byte[] array3 = new byte[2 * _1MB];  // gc1
    
        array3 = new byte[2 * _1MB];
        array3 = new byte[2 * _1MB];
        array3 = new byte[300 * 1024];
        array3 = null;
    
        System.out.println("------------2------------");
        byte[] array4 = new byte[2 * _1MB]; // gc2
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    jvm参数

    -XX:NewSize=10m
    -XX:MaxNewSize=10m
    -XX:InitialHeapSize=20m
    -XX:MaxHeapSize=20m
    -XX:SurvivorRatio=8
    -XX:PretenureSizeThreshold=10m
    -XX:MaxTenuringThreshold=15
    -XX:+UseParNewGC
    -XX:+UseConcMarkSweepGC
    -XX:+PrintGCDetails
    -XX:+PrintGCTimeStamps
    -XX:+PrintTenuringDistribution
    -XX:+PrintHeapAtGC
    -XX:+PrintGCApplicationStoppedTime
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第一次垃圾回收新生代7420K减少到了794K,其中有300K我们引用的,还有494K是系统运行创建的,在经过第一次垃圾回收的时候,他们都被放在幸存区,age=1,此时Desired survivor size = 524288bytes,而当前幸存区存活的age =1 占用了780672,则新的threshold=1,在下一次垃圾回收时,会将age=1的直接进入老年代

    ------------1------------
    {Heap before GC invocations=0 (full 0):
     par new generation   total 9216K, used 7420K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 8192K,  90% used [0x00000007be800000, 0x00000007bef3f158, 0x00000007bf000000)
      from space 1024K,   0% used [0x00000007bf000000, 0x00000007bf000000, 0x00000007bf100000)
      to   space 1024K,   0% used [0x00000007bf100000, 0x00000007bf100000, 0x00000007bf200000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3221K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 344K, capacity 388K, committed 512K, reserved 1048576K
    0.055: [GC (Allocation Failure) 0.055: [ParNew
    Desired survivor size 524288 bytes, new threshold 1 (max 15)
    - age   1:     780672 bytes,     780672 total
    : # 7420K->794K(9216K), 0.0007712 secs] 7420K->794K(23552K), 0.0007927 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
    Heap after GC invocations=1 (full 0):
     par new generation   total 9216K, used 794K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 8192K,   0% used [0x00000007be800000, 0x00000007be800000, 0x00000007bf000000)
      from space 1024K,  77% used [0x00000007bf100000, 0x00000007bf1c6b38, 0x00000007bf200000)
      to   space 1024K,   0% used [0x00000007bf000000, 0x00000007bf000000, 0x00000007bf100000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3221K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 344K, capacity 388K, committed 512K, reserved 1048576K
    }
    0.056: Total time for which application threads were stopped: 0.0009190 seconds, Stopping threads took: 0.0000282 seconds
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    新增4.3M后新生代占用了7398K,新加入的4.3M全部进入eden

    在新增2M进入第二次minor gc,新生代内存占用减少到了37K,堆内存占用了759K,其中上一次gc age = 1 的 780772bytes直接进入了老年区,并且可以观察到当前幸存区有age = 1 占用了96bytes,并且new threshold = 15,恢复了原来的阈值,这说明
    ∑ a g e = 1 15 t a b l e S i z e [ a g e ] ( T a r g e t S u r v i v o r R a t i o ∗ S 0 / 100 ) \sum_{age=1}^{15} tableSize[age]( TargetSurvivorRatio * S0 / 100 ) age=115tableSize[age](TargetSurvivorRatioS0/100)
    公式在每次gc后都会进行动态计算,计算出如果(1)的结果>Desired survivor size( TargetSurvivorRatio * S0 / 100 ) = (50*1024/100)K*1024bytes = 524288,则会走min(age,MaxTenuringThreshold),重置new threshold的值

    如果没有修改参数 -XX:TargetSurvivorRatio 则默认是50

    jinfo -flag TargetSurvivorRatio PID  # 可以使用jinfo -flag 参数名 进程号 进行验证
    
    • 1
    ------------2------------
    {Heap before GC invocations=1 (full 0):
     par new generation   total 9216K, used 7398K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 8192K,  80% used [0x00000007be800000, 0x00000007bee730b0, 0x00000007bf000000)
      from space 1024K,  77% used [0x00000007bf100000, 0x00000007bf1c6b38, 0x00000007bf200000)
      to   space 1024K,   0% used [0x00000007bf000000, 0x00000007bf000000, 0x00000007bf100000)
     concurrent mark-sweep generation total 14336K, used 0K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3222K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 344K, capacity 388K, committed 512K, reserved 1048576K
    0.056: [GC (Allocation Failure) 0.056: [ParNew
    Desired survivor size 524288 bytes, new threshold 15 (max 15)
    - age   1:         96 bytes,         96 total
    : # 377398K->37K(9216K), 0.0016388 secs] 7398K->759K(23552K), 0.0016555 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
    Heap after GC invocations=2 (full 0):
     par new generation   total 9216K, used 37K [0x00000007be800000, 0x00000007bf200000, 0x00000007bf200000)
      eden space 8192K,   0% used [0x00000007be800000, 0x00000007be800000, 0x00000007bf000000)
      from space 1024K,   3% used [0x00000007bf000000, 0x00000007bf009520, 0x00000007bf100000)
      to   space 1024K,   0% used [0x00000007bf100000, 0x00000007bf100000, 0x00000007bf200000)
     concurrent mark-sweep generation total 14336K, used 722K [0x00000007bf200000, 0x00000007c0000000, 0x00000007c0000000)
     Metaspace       used 3222K, capacity 4496K, committed 4864K, reserved 1056768K
      class space    used 344K, capacity 388K, committed 512K, reserved 1048576K
    }
    0.058: Total time for which application threads were stopped: 0.0017115 seconds, Stopping threads took: 0.0000058 seconds
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    显然开头引用的 survivor区中,如果相同年龄的所有对象大小所占用的空间大于survivor空间的一半,年龄大于或等于该年龄对象的,都可以直接进入老年代显然是错误的

    应该是在survivor区中,所有年龄的对象的所占空间的累加和大于survivor空间的一半,大于或等于该年龄的对象,都可以进入老年代

    在这里有关于下面这条公式的源码,有兴趣的小伙伴可以看看
    ∑ a g e = 1 15 t a b l e S i z e [ a g e ] ( T a r g e t S u r v i v o r R a t i o ∗ S 0 / 100 ) \sum_{age=1}^{15} tableSize[age]( TargetSurvivorRatio * S0 / 100 ) age=115tableSize[age](TargetSurvivorRatioS0/100)

    uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
        //survivor_capacity是survivor空间的大小
      //desired_survivor_size就是动态年龄判断是否对象进入老年代的阈值
      //TargetSurvivorRatio:默认50
      size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
      size_t total = 0;
      uint age = 1;
      while (age < table_size) {
      //sizes数组是每个年龄段对象大小
      //total就是年龄从小到大的对象占据的空间累加和
        total += sizes[age];
      //如果累加和大于阈值,就直接跳出循环,假设磁盘的age等于3吧
        if (total > desired_survivor_size) break;
        age++;
      }
      //MaxTenuringThreshold上限是15,现在age是3,那么result就是等于3
      uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
        ...
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    以上内容仅为个人见解,如果有其他疑问,欢迎评论区留言

  • 相关阅读:
    Qt如何在视频画面上新增车道线显示
    centos7桌面版下载向日葵和todesk
    ADOP带您了解什么是光纤跳线
    将transformers的tokenizer处理之后(如BPE)的序列映射回输入序列
    ACWing471. 棋盘-DFS剪枝
    python的环境安装(版本3.10.6)
    C++入门教程||C++while循环
    ubuntu| ubuntu 20、ubuntu高版本安装低版本的gcc、gcc5
    每日一题——输入一个日期,输出它是该年的第几天
    字体属性 font
  • 原文地址:https://blog.csdn.net/weixin_46195957/article/details/127685410