JVM-对象进入老年代的四种方式-1(实战篇)

214 阅读4分钟

对象进入老年代的四种方式

  • 大对象
  • 动态年龄判断
  • minor gc后,survivor区空间不能容纳全部存活对象
  • 存活对象达到年龄阈值。比如15

这一节主要讲:minor gc后,survivor区空间不能容纳全部存活对象

直接上代码:

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
    }

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 -Xloggc:survivor_live.log

运行代码后的日志信息:

Java HotSpot(TM) 64-Bit Server VM (25.281-b09) for bsd-amd64 JRE (1.8.0_281-b09), built on Dec  9 2020 12:44:49 by "java_re" with gcc 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.11.45.5)
Memory: 4k page, physical 16777216k(107748k free)

/proc/meminfo:

CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 

0.127: [GC (Allocation Failure) 0.127: [ParNew: 6943K->327K(9216K), 0.0030105 secs] 6943K->2377K(19456K), 0.0032581 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 par new generation   total 9216K, used 2458K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,  26% used [0x00000007bec00000, 0x00000007bee14930, 0x00000007bf400000)
  from space 1024K,  32% used [0x00000007bf500000, 0x00000007bf551fb8, 0x00000007bf600000)
  to   space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
 concurrent mark-sweep generation total 10240K, used 2050K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
 Metaspace       used 2713K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 291K, capacity 386K, committed 512K, reserved 1048576K

接下来,我们来分析代码和日志:

byte[] array1 = new byte[2*_1MB];
        array1 = new byte[2*_1MB];
        array1 = new byte[2*_1MB];//假设这个是C对象

        byte[] array2 = new byte[128*1024];
        array2 = null;

这里创建了3个2m对象,1个128k的对象。最终array1指向C对象,array2置为null。对应的堆图如下所示:

此时日志所示: ParNew: 6943K->327K(9216K)

这个时候eden区已使用6943K。

此时要执行byte[] array3 = new byte[2*_1MB];

这个时候因为可使用空间只有8m,如果要分配2m空间,此时会触发young gc。

ParNew: 6943K->327K(9216K)

因为只有array1指向了1一个2M对象,其他对象都会被回收掉。

GC后,新生代只剩下327K。这个时候你可能会问:2m对象去哪里了呢?

concurrent mark-sweep generation total 10240K, used 2050K

你看,2m对象在老年代中。为什么会在老年代中。

因为新生代分配了10m空间,然后survivor区只占1m空间。

1m的空间是无法容纳2m的对象的。

因此,对象直接进入老年代。

说到这里,我们已经简单 地用代码证明了:minor gc后,survivor区空间不能容纳全部存活对象

有一些细心的同学可能会问:当survivor空间不足时,存活对象都全部进入老年代吗?survivor区能容纳的对象,可否放在survivor区,不能容纳的对象,才放到老年代。

这个问题,我们后续会有专门文章解答。大家先思考一下。