您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~
上一次留了个小尾巴:怎么以通过代码模拟对象年龄在15岁之后才进入老年代呢?自己试着实现了一下。
首先需要设置好相关的JVM环境:
-XX:InitialHeapSize=104857600 -XX:MaxHeapSize=104857600 -XX:NewSize=20971520 -XX:MaxNewSize=20971520 -XX:MaxTenuringThreshold=15 -XX:SurvivorRatio=6 -XX:PretenureSizeThreshold=10485760 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/work/logs/gc.log
然后准备示例代码:
public static void main(String[] args) {
byte[] array1 = new byte[128 * 1024];
for (int i = 0; i < 20; i++) {
byte[] array2 = new byte[4 * 1024 * 1024];
array2 = new byte[4 * 1024 * 1024];
array2 = new byte[3 * 1024 * 1024];
array2 = null;
}
}
运行后,打印出来的GC日志如下:
可以清除地看到,第15次时,还有存活对象在年轻代。而第16次时,年轻代中已没有任何存活对象。从第16次开始,存活对象始终存在于老年代,大小固定为732K。这就实现了预期的结果。
下面再来动手实现「Survivor空间不足新对象直接进入老年代」的目标。
还是老套路,准备相关JVM设置参数:
-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=10485760 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/work/logs/gc.log
然后准备示例代码:
public static void main(String[] args) {
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
byte[] array2 = new byte[128 * 1024];
array2 = null;
byte[] array3 = new byte[2 * 1024 * 1024];
}
代码执行前的内存分配是这样的:
首先执行下面的代码:
byte[] array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
array1 = new byte[2 * 1024 * 1024];
执行过后,创造了3 × 2M的数组,JVM状态为:
接着执行:
byte[] array2 = new byte[128 * 1024];
array2 = null;
byte[] array3 = new byte[2 * 1024 * 1024];
此时eden空间不足,触发Young GC:
从打印出来的GC日志可以看到:array3分配到eden区,由于S0和S1都放不下array1,它被直接分配到老年代:
1、Eden区使用了26%,正是array3对象;
2、Survivor From使用了60% = 625K / 1024K,保存存活对象;
3、老年代被使用了2050K,存放的是array1对象。
再来一个实例:通过代码模拟分配一个大对象,让大对象直接进入老年代。
相关JVM参数:
-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=1048576 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/work/logs/gc.log
示例代码:
public static void main(String[] args) {
byte[] array = new byte[2 * 1024 * 1024];
array = null;
}
通过GC日志可以看到:
1、Eden区有一些未知对象;
2、由于array对象大小超过-XX:PretenureSizeThreshold指定的指,所以没有触发任何GC,array被直接分配到了老年代。
继续实例:老年代空间不足导致触发GC。
相关JVM参数:
-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/home/work/logs/gc.log
示例代码:
public static void main(String[] args) {
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];
byte[] array6 = new byte[2 * 1024 * 1024];
}
代码执行前内存分配:
执行代码:
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;
执行后,创造了1 × 4M的数组,JVM状态为:
接着继续执行下列代码:
byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];
此时的JVM内存状态:
再继续执行:
byte[] array6 = new byte[2 * 1024 * 1024];
此时eden空间不足,触发Young GC,此时的JVM内存状态:
观察GC日志:
「ParNew (promotion failed): 7419K->8166K(9216K), 0.0023952 secs」
这一行说明回收失败,array2、array3、array4、array5、array6都被引用,一个都回收不掉,空间超过8M。尝试往老年代中存放,此时老年代已有一个4M大小的array1,于是老年代也放不下,触发Full GC。
「CMS: 8194K->6864K(10240K), 0.0026125 secs 11515K->6864K(19456K), [Metaspace: 2755K->2755K(1056768K)], 0.0051233 secs」
这一行说明此时执行了CMS GC,CMS: 8194K->6864K(10240K),表明老年代空间从8M变为6M。同时触发元空间的GC(Metaspace GC)
「CMS: 8194K->6864K(10240K)」
这一行说明:
1、先将array3和array4放入老年代;
2、触发CMS的Full GC,回收掉无用的数组array1;
3、将array2和arrya5再放进去;
4、因此老年代的大小就是array2 + array3 + array4 + array5 = 3 × 2M + 128K;
5、再将array6放到eden区。
Full GC的执行过程是:
查看Full GC日志:
可以知道:
1、Eden区使用了26%,正是array6对象;
2、老年代被使用了6864K,存放的是array2~array5对象。
再留几个小尾巴,用代码模拟出另外几种老年代GC场景:
1、触发Young GC前,老年代空间小于历次Young GC后升入老年代的对象的平均大小;
2、老年代被使用率达到92%的阈值。
一些FAQ。
关于reserved、committed、capacity、used和class space的概念澄清:
1、它们并不是纯粹的JVM概念,也和OS紧密相关;
2、reserved是操作系统为JVM进程“保留”的连续的空间,它只是记录JVM需要多少空间,并不会被提交给OS;
3、committed是当JVM进程真正要使用这个连续的空间时,OS真正分配的(也可能会分配失败);
4、capacity和used才属于JVM;
5、capacity反映了容量,used反映了实际使用量;
6、metaspace并不是将全部的空间都用来存放class对象,它还需要放所谓静态变量(比如一个Class Loader被分配了一块内存,这块内存可能并没有被用完,于是就会有产生内存碎片);
7、因此,class space是指实际上被用于放class对象的那块内存的和。
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~