对象进入老年代的四种方式
- minor gc后,survivor区空间不能容纳全部存活对象
- 存活对象达到年龄阈值。比如15
- 大对象
- 动态年龄判断
动态年龄判断
首先,我们还是先了解一下什么是动态年龄判断?
书本的解释如下:
总结一下:就是说survivor区中,如果相同年龄的所有对象大小所占用的空间大于survivor空间的一半,年龄大于或等于该年龄对象的,都可以直接进入老年代。
这是书本的说法。。
但其实这个说法是错误的。
相信看过我另一篇文章JVM-动态年龄判断的小伙伴,是了解正确的说法应该是:在survivor区中,所有年龄的对象的所占空间的累加和大于survivor空间的一半,大于或等于该年龄的对象,都可以进入老年代。
接下来我们直接上代码和JVM配置参数:
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:dynamicheck.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(106332k 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.117: [GC (Allocation Failure) 0.118: [ParNew: 7115K->619K(9216K), 0.0029447 secs] 7115K->619K(19456K), 0.0033789 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.122: [GC (Allocation Failure) 0.122: [ParNew: 7223K->0K(9216K), 0.0020215 secs] 7223K->601K(19456K), 0.0020509 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 2212K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 27% used [0x00000007bec00000, 0x00000007bee290e0, 0x00000007bf400000)
from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
concurrent mark-sweep generation total 10240K, used 601K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
Metaspace used 2713K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 291K, capacity 386K, committed 512K, reserved 1048576K
部分代码剖析:
现在连续分配了3个2M的对象,和1个300K的对象,最后还把array1置为null。此时的堆图应该是这样子的
接下来,还要继续分配一个2M的对象,这个时候eden区还能继续分配空间吗?
肯定不可以,因为eden区只有8M。
那这个时候,只好执行young gc来清理空间了。
接着,我们看一下日志文件:
ParNew: 7115K->619K(9216K)
说明GG前,占用了7115K,这里大概包括3个2m对象+300k对象+几百K未知对象。经过GC后,只剩下619K对象,包括300K对象和未知对象。
然后给新创建的2m对象分配到eden区
此时,我们得观察一个重点:就是from区是1m,就是1024K。现在呢,有619K对象已经来到了from区,是超过from区的一半的。
接着我们继续看代码:
执行完这个代码。会在堆里会新增2个2m对象,1个300K对象,最后array3置为null。
如果要继续执行byte[] array4 = new byte[2*_1MB];
那么eden区就不够空间了。这个时候会触发第二次young gc了。
我们继续看一下第二次young gc的日志:
ParNew: 7223K->0K(9216K)
说明了啥?
GC前,一共使用了7223K,包括eden区的3个2m对象+300K对象和from区的300K对象+未知对象,GC后,整个新生代都有空了。
理论上,GC后,array2还引用着300K对象的。所以,可以肯定的是,这300K对象,肯定不会被回收。
但现在GC日志明显告诉我们,新生代在GC后的空间使用率为0。
这是为什么呢?
不着急,我们继续看一下老年代的空间日志:
concurrent mark-sweep generation total 10240K, used 601K
你看,老年代竟然被使用了601K。其实这601K,就是300K对象和未知对象的空间。
为什么它们会在老年代?
就是因为触发了动态年龄判断呀。
你想想,首先,300K不是大对象吧。(-XX:PretenureSizeThreshold=10m)
也没到达15岁,因为才young gc两次。(-XX:MaxTenuringThreshold=15)
而且现在survivor是1024K空间,是足以容纳601K的存活对象的。所以,这些都不是导致对象进入老年代的原因。
现在因为2次young gc,那600K对象都存活着,并且占用的空间是超过survivor区的空间大小一半。从而触发动态空间判断,进入老年代。
说到这里,相信大家应该都能明白了。如果还不明白,欢迎加大头菜微信一起探讨。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。