《深入理解java虚拟机v3》 空间分配担保 代码清单3-11

在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那这一次Minor GC可以确保是安全的。

如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置值是否允许担保失败(Handle Promotion Failure);如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者-XX:HandlePromotionFailure设置不允许冒险,那这时就要改为进行一次Full GC

解释一下“冒险”是冒了什么风险:前面提到过,新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况
——最极端的情况就是内存回收后新生代中所有对象都存活,需要老年代进行分配担保,把Survivor无法容纳的对象直接送入老年代,这与生活中贷款担保类似。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,但一共有多少对象会在这次回收中活下来在实际完成内存回收之前是无法明确知道的,所以只能取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

取历史平均值来比较其实仍然是一种赌概率的解决办法,也就是说假如某次Minor GC存活后的对象突增,远远高于历史平均值的话,依然会导致担保失败。如果出现了担保失败,那就只好老老实实地重新发起一次Full GC,这样停顿时间就很长了。虽然担保失败时绕的圈子是最大的,但通常情况下都还是会将-XX:HandlePromotionFailure开关打开,避免Full GC过于频繁。

参见代码清单3-11,请读者先以JDK 6 Update 24之前的HotSpot运行测试代码。

代码中是JDK1.6 24后的版本执行过程的注释,等价于开启参数-XX:HandlePromotionFailure=true

private static final int _1MB = 1024 * 1024;

/**
 * -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * -XX:-HandlePromotionFailure  -XX:+UseSerialGC
 */
public static void testHandlePromotion() {
    
    
	byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
	allocation1 = new byte[2 * _1MB]; // 进入Eden区
	allocation2 = new byte[2 * _1MB]; // 进入Eden区
	allocation3 = new byte[2 * _1MB]; // 进入Eden区
	allocation1 = null;
	// 由于Eden区最大支持8M,已经6M,不够再放入2M, 触发minor gc,因为老年代可用空间10M>6M,担保成功
	// gc后,allocation1销毁,allocation2、allocation3进入老年代,共占用4M,老年代剩余6M
	// allocation5 进入Eden区
	allocation4 = new byte[2 * _1MB];

	allocation5 = new byte[2 * _1MB]; // 进入Eden区,此时共占用4M

	allocation6 = new byte[2 * _1MB]; // 进入Eden区,此时共占用6M
	allocation4 = null;
	allocation5 = null;
	allocation6 = null;
	// eden区已经占用6M,再次触发minor gc
	// 因为老年代剩余6M,虽然不够放入整个eden区+survivor(6M+148K),但是大于历史进入的值4M
	//gc后,allocation4、allocation5、allocation6被删除,allocation7进入Eden区
	allocation7 = new byte[2 * _1MB];
}

public static void main(String[] args) {
    
    
	testHandlePromotion();
}

以-XX:HandlePromotionFailure=false参数来运行的结果,没有24之前的版本,直接拷贝书上的:

[GC [DefNew: 6651K->148K(9216K), 0.0078936 secs] 6651K->4244K(19456K), 0.0079192 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]
[GC [DefNew: 6378K->6378K(9216K), 0.0000206 secs][`Tenured`: 4096K->4244K(10240K), 0.0042901 secs] 10474K->4   `//full gc`

可以看到第二次gc是full gc

以-XX:HandlePromotionFailure=true参数来运行的结果:

[`GC`[`DefNew`: 6487K->147K(9216K), 0.0050823 secs] 6487K->4243K(19456K), 0.0051264 secs] [Times: user=0.00 sys=0.02, real=0.02 secs]   `//minor gc`
[`GC` [`DefNew`: 6377K->147K(9216K), 0.0013094 secs] 10473K->4243K(19456K), 0.0013778 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]   `//minor gc`
Heap
 def new generation   total 9216K, used 2359K [0x32360000, 0x32d60000, 0x32d60000)
  eden space 8192K,  27% used [0x32360000, 0x32588fe0, 0x32b60000)    `allocation7进入Eden区`
  from space 1024K,  `14% used` [0x32b60000, 0x32b84d70, 0x32c60000)   `survivor 148K`
  to   space 1024K,   0% used [0x32c60000, 0x32c60000, 0x32d60000)
 tenured generation   total 10240K, used 4096K [0x32d60000, 0x33760000, 0x33760000)
   the space 10240K,  40% used [0x32d60000, 0x33160020, 0x33160200, 0x33760000)   `allocation2、allocation3共4M`
 compacting perm gen  total 12288K, used 380K [0x33760000, 0x34360000, 0x37760000)
   the space 12288K,   3% used [0x33760000, 0x337bf220, 0x337bf400, 0x34360000)
    ro space 10240K,  51% used [0x37760000, 0x37c925d0, 0x37c92600, 0x38160000)
    rw space 12288K,  55% used [0x38160000, 0x387fd978, 0x387fda00, 0x38d60000)
Warning: The flag -HandlePromotionFailure has been EOL'd as of 6.0_24 and will be ignored

在JDK 6 Update 24之后,这个测试结果就有了差异,-XX:HandlePromotionFailure参数不会再影响到虚拟机的空间分配担保策略,观察OpenJDK中的源码变化(见代码清单3-12),虽然源码中还定义了-XX:HandlePromotionFailure参数,但是在实际虚拟机中已经不会再使用它。JDK 6 Update 24之后的规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行
Minor GC,否则将进行Full GC。

等同于在JDK 6 Update 24之后,默认设置-XX:HandlePromotionFailure=true

猜你喜欢

转载自blog.csdn.net/m0_45406092/article/details/108675083
今日推荐