目录
1、什么是大对象以及大对象对垃圾回收的影响
2、什么情况下对象会进入老年代
2.1 当创建对象的大小超过-XX:PretenureSizeThreshold的设定值
2.2 长期存活的对象将进入老年代
2.3 动态年龄判定
2.4 空间担保分配
什么是空间分配担保?
为什么要进行空间担保?
1、什么是大对象以及大对象对垃圾回收的影响
大对象是指需要大量连续内存空间的Java对象,最典型的就是很长的字符串,或者很大的数组对象。在实际开发中我们要尽量避免大对象,因为在分配内存空间时,明明有很大的空间,但是连续的空间不足以创建大对象时,JVM不得不提前进行垃圾回收,以获取足够的空间来创建大对象;第二个原因,创建大对象在复制对象的时候需要高额的内存复制开销。
2、什么情况下对象会进入老年代
对象进入老年代这里不考虑ZGC(ZGC并没有设置分代)的情况。
2.1 当创建对象的大小超过-XX:PretenureSizeThreshold的设定值
HotSpot虚拟机提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接在老年代分配,这样做的目的是避免在Eden和两个Survivor区之间来回复制,产生大量的内存复制操作。
注:-XX:PretenureSizeThreshold参数只对Serial和ParNew新生代的收集器有效。
代码验证
package com.wssnail.test;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 测试超过PretenureSizeThreshold设定值直接进入老年代
* 本代码在jdk8环境下运行 3145728=3*1024*1024
* -Xms20M -Xmx20M -Xmn10M -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:PreTenureSizeThreshold=3145728 -verbose:gc
* -XX:+PrintCommandLineFlags
* @date 2023/4/9 16:00
*/
public class TestPretenureSizeThreshold {
private static final int _1Mb = 1024 * 1024;
public static void main(String[] args) {
byte[] allocation;
allocation = new byte[4 * _1Mb];
}
}
-Xms20M
-Xmx20M
-Xmn10M
-XX:+UseConcMarkSweepGC
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=3145728
-verbose:gc
-XX:+PrintCommandLineFlags
从上图可以看出,4MB的对象直接分配到了老年代中,占用了4096KB,没有在新生代进行分配。
2.2 长期存活的对象将进入老年代
这里长期存活的对象是指采用分代收集来管理内存,每经过一次Minor GC而没有进入老年代,当经历过MaxTenuringThreshold设定的阈值(默认是15)之后,会进入老年代。
关注公众号熟透的蜗牛,领取面试资料
代码验证
package com.wssnail.test;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 测试超过PretenureSizeThreshold设定值直接进入老年代
* 本代码在jdk8环境下运行
* -Xms20M -Xmx20M -Xmn10M -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintCommandLineFlags -XX:+PrintTenuringDistribution
* @date 2023/4/9 16:00
*/
public class TestMaxTenuringThreshold {
private static final int _1Mb = 1024 * 1024;
public static void main(String[] args) {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1Mb / 4]; //256kb 进入老年代跟MaxTenuringThreshold的值有关
allocation2 = new byte[_1Mb * 4];
allocation3 = new byte[_1Mb * 4];
allocation3 = null;
allocation3 = new byte[_1Mb * 4];
}
}
MaxTenuringThreshold=1
MaxTenuringThreshold=15
MaxTenuringThreshold该参数取值0-15,由于对象头中只有4个二进制位来存储分代年龄,所以该值最大只能是15,超过该范围报错。
2.3 动态年龄判定
HotSpot虚拟机并不是要求对象的年龄必须达到-XX:MaxTenuringThreshold设定的阈值才能晋升到老年代,如果在Survivor空间中低于或者等于某年龄的所有的对象的大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以进入老年代,而无须等-XX:MaxTenuringThreshold设定的值。比如Survivor总大小为1M,而Survivor空间中超过5岁的对象的大小超过了0.5M,那么只要对象大于等于5岁就可以直接进入老年代。
关注公众号熟透的蜗牛,领取面试资料
2.4 空间担保分配
JVM使用分代收集算法,将堆内存划分为年轻代和老年代,两块内存分别采用不同的垃圾回收算法,空间担保指的是老年代进行空间分配担保。
什么是空间分配担保?
- 在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。
- 如果大于,则此次Minor GC是安全的
- 如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
为什么要进行空间担保?
是因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。但是假如某一次的MinorGC后存活的对象激增,超过了历史平均值,那么就有可能会导致担保失败,如果担保失败了,那么就不得不进行一次Full GC ,这样导致的结果就是停顿时间变长。
关注公众号熟透的蜗牛,领取面试资料
JDK 6 Update 24之后-XX:HandlePromotionFailure参数不会影响虚拟机的空间担保策略,更改之后的规则为只要老年代的连续空间大于新生代对象总大小或者大于历次晋升的平均大小,就会执行Minor GC。否则进行Full GC。