JAVA 空间分配担保

本内容节选自《深入理解java虚拟机》
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。
下面解释一下“冒险”是冒了什么风险,前面提到过,新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况(最极端的情况就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。
取平均值进行比较其实仍然是一种动态概率的手段,也就是说,如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败(Handle Promotion Failure)。如果出现了HandlePromotionFailure失败,那就只好在失败后重新发起一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁,参见如下代码,请读者在JDK 6 Update 24之前的版本中运行测试。

代码:

空间分配担保:
private static final int _1MB = 1024 * 1024;
/**
 * VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:-HandlePromotionFailure
 */
@SuppressWarnings("unused")
public static void testHandlePromotion() {
  byte[] allocation1, allocation2, allocation3, allocation4, allocation5, allocation6, allocation7;
  allocation1 = new byte[2 * _1MB];
  allocation2 = new byte[2 * _1MB];
  allocation3 = new byte[2 * _1MB];
  allocation1 = null;
  allocation4 = new byte[2 * _1MB];
  allocation5 = new byte[2 * _1MB];
  allocation6 = new byte[2 * _1MB];
  allocation4 = null;
  allocation5 = null;
  allocation6 = null;
  allocation7 = new byte[2 * _1MB];
}

以HandlePromotionFailure = false参数来运行的结果:
[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->4244K(19456K), [Perm : 2104K->2104K(12288K)], 0.0043613 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

([DefNew:新生代,[Tenured:老年代,[Perm:永久代)

分析:

DefNew第一次GC发生在{allocation1 = null;}之后,出现了一次Minor GC

DefNew内存变化6651K->148K(9216K),新生代的对象allocation1allocation2进入老年代,allocation3

堆内存变为6651K->4244K(19456K),allocation1 = null,所以堆内存减少[2 * _1MB]

DefNew第二次GC发生在{allocation6 = null;}之后,出现了一次Minor GC一次Major GC

DefNew内存变化6378K->148K(9216K),allocation4 = null,allocation5= null,allocation6 = null被清理

Tenured内存变化4096K->4244K(10240K),(不太清楚)

堆内存变化10474K->4244K(19456K),

Perm内存变为2104K->2104K(12288K),为{allocation7 = new byte[2 * _1MB]}


以HandlePromotionFailure = true参数来运行的结果:
[GC [DefNew: 6651K->148K(9216K), 0.0054913 secs] 6651K->4244K(19456K), 0.0055327 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 6378K->148K(9216K), 0.0006584 secs] 10474K->4244K(19456K), 0.0006857 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

分析:

DefNew第一次GC发生在{allocation1 = null;}之后,出现了一次Minor GC

DefNew内存变化6651K->148K(9216K),新生代的对象allocation1allocation2进入老年代,allocation3被清理

堆内存变化6651K->4244K(19456K),allocation1 = null,所以堆内存减少[2*_1MB]

DefNew第二次GC发生在{allocation6 = null;}之后,出现了一次Minor GC,一次Major GC

DefNew内存变化6378K>148K(9216K),新生代的对象allocation4allocation5allocation6被清理

堆内存变化10474K->4244K(19456K),allocation4 = null,allocation5= null,allocation6 = null,所以堆内存减少[6*_1MB]

 

其实使用=null的方式并不是真的清空对象,对象只是被回收,但是数组空间仍然存在,只是提示jvm我下次可以被回收了,而gc就直接是清理,

以上分析方面的内容仅为个人理解。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值