21_模拟对象进入老年代
1636字约5分钟
2024-08-10
对象进入老年代的四个常见时机
1、躲过
15
次 GC 后进入老年代2、动态年龄判定规则,
Survivor
区内年龄1
+年龄2
+年龄n
的对象总和大于Survivor
区的50%
,年龄n
及以上对象进入老年代3、
Young GC
过后存活对象太多无法放入Survivor
区,直接进入老年代4、大对象直接进入老年代
我们这里通过代码模拟最常见的一种对象进入老年代的情况,动态年龄判定规则
JVM 参数配置
下面这些参数基于 JDK1.8
版本来配置的,忘记怎么设置的可以回看 07_动手实验:系统部署时如何设置JVM内存大小 的内容
//初始新生代大小 最大新生代大小 初始堆内存大小 最大堆内存大小 Eden区占比新生代比例 大对象阈值 新生代垃圾器 ParNew 老年代垃圾器 CMS 打印详细 GC 日志 打印每次发生GC的时间 将GC 日志写入磁盘文件
-XX:NewSize=10M -XX:MaxNewSize=10M -XX:InitialHeapSize=20M -XX:MaxHeapSize=20M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
模拟对象进入老年代部分代码
我们先看模拟对象进入老年代部分代码,最后再结合完整代码一起看
public static void main(String[] args) {
System.gc();
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[512 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
}
System.gc();
这行代码是手动调用 GC
,前面模拟 Young GC
时我们看到 main()
方法执行的时候也占用了一定的内存,对于我们直接看 GC
日志有一些影响
通过 new byte[2 * 1024 * 1024]
连续分配 3
个 2MB
数组,array1
这个局部变量依次引用这三个对象,最后 array1
指向 null
给 array2
局部变量分配一个 512KB
数组,在我们指定的 JVM
参数中,新生代 10MB
,Eden
区大小为 8MB
,此时 Eden
区已经分配了 6.5MB
,如下图所示
再给局部变量 array3
分配 2MB
的数组,显然 Eden
区是放不下的,此时就会触发一次 Young GC
,array2
引用对象就会进入 Survivor
区
Java HotSpot(TM) 64-Bit Server VM (25.92-b14) for windows-amd64 JRE (1.8.0_92-b14), built on Mar 31 2016 21:03:04 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 25038040k(4546628k free), swap 47862644k(14559984k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -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:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.145: [Full GC (System.gc()) 0.145: [CMS: 0K->1125K(10240K), 0.0029815 secs] 3386K->1125K(19456K), [Metaspace: 3336K->3336K(1056768K)], 0.0030904 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.150: [GC (Allocation Failure) 0.150: [ParNew: 6819K->512K(9216K), 0.0003237 secs] 7945K->1637K(19456K), 0.0003533 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 2642K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
from space 1024K, 50% used [0x00000000ff500000, 0x00000000ff580010, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 1125K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3343K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K
下面这行 Full GC
日志不用管,是我们手动调用的 gc
0.145: [Full GC (System.gc()) 0.145: [CMS: 0K->1125K(10240K), 0.0029815 secs] 3386K->1125K(19456K), [Metaspace: 3336K->3336K(1056768K)], 0.0030904 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC
前新生代内存使用 6819K
,我们分配了 5MB + 512KB = 5632KB
,算上一些其他信息占用的内存,和我们预期差不多
GC
过后新生代内存使用 512KB
,这是我们 array2
数组还引用着对象导致的,并且 GC
过后这 512KB
是放在 Survivor
区的
0.150: [GC (Allocation Failure) 0.150: [ParNew: 6819K->512K(9216K), 0.0003237 secs] 7945K->1637K(19456K), 0.0003533 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
JVM
退出时候打印的当前堆内存使用情况,最后分配的 array3
的 2MB
数组,占比 Eden
区 26%
,array2
引用的 512KB
数组占用 Survivor
区 50%
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
from space 1024K, 50% used [0x00000000ff500000, 0x00000000ff580010, 0x00000000ff600000)
模拟对象进入老年代完整代码
我们继续完善一下模拟的代码,我们继续给 array3
局部变量分配 2
个 2MB
数组,最后指向 null
当我们给 array4
局部变量再分配 1
个 2MB
数组时,Eden
区的内存是分配不下的,就会触发一次 Young GC
public static void main(String[] args) {
System.gc();
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[512 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = new byte[2 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[2 * 1024 * 1024];
}
这里我们不再一段一段分析 GC
日志了,我们直接看最后一次 GC
[ParNew: 6865K->0K(9216K), 0.0003258 secs]
,这里新生代内存使用 GC
过后直接变成了 0
,这可能吗,我们的 array2
还引用着对象的
我们之前为什么要看部分模拟代码执行呢,原因就在这,我们可以看看之前 GC
时候老年代内存使用情况,方便我们现在来对比
现在来看,array2
引用的 512KB
数组直接从 Survivor
进入老年代了,最后 array4
局部变量引用的数组对象占用 Eden
区 26%
// 部分模拟代码 JVM 退出时打印的老年代内存使用情况
concurrent mark-sweep generation total 10240K, used 1125K
// 完整模拟代码 JVM 退出时打印的老年代内存使用情况
concurrent mark-sweep generation total 10240K, used 1650K
Java HotSpot(TM) 64-Bit Server VM (25.92-b14) for windows-amd64 JRE (1.8.0_92-b14), built on Mar 31 2016 21:03:04 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 25038040k(5621492k free), swap 47862644k(15775580k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -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:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.139: [Full GC (System.gc()) 0.139: [CMS: 0K->1099K(10240K), 0.0034014 secs] 3386K->1099K(19456K), [Metaspace: 3314K->3314K(1056768K)], 0.0035015 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.144: [GC (Allocation Failure) 0.144: [ParNew: 6983K->584K(9216K), 0.0003928 secs] 8083K->1683K(19456K), 0.0004248 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.145: [GC (Allocation Failure) 0.145: [ParNew: 6865K->0K(9216K), 0.0003258 secs] 7965K->1650K(19456K), 0.0003503 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
concurrent mark-sweep generation total 10240K, used 1650K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 3343K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 365K, capacity 388K, committed 512K, reserved 1048576K