GC悲观策略之Parallel GC篇

先来看段代码:

import java.util.ArrayList;
import java.util.List;
 
/**
 * -Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC
 */
public class Test1 {
 
    public static void main(String[] args) throws InterruptedException {
       final  int _1m=1024*1024;
        List<byte[]> list = new ArrayList<>();

        for (int i = 0; i <7; i++) {
            list.add(new byte[3*_1m]);
        }
        list.clear();
        for (int i = 0; i <2; i++) {
            list.add(new byte[3*_1m]);
        }      
        Thread.sleep(10000);      
    }
}


       当用-Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC 执行上面的代码时会执行几次Minor GC和几次Full GC呢?
按照eden空间不足时触发minor gc的规则,上面代码执行后的GC应为:M、M、M、M ,但实际上上面代码执行后GC则为:M、M、M、F、F 。具体gc日志如下:

[GC (Allocation Failure) [PSYoungGen: 5286K->712K(9216K)] 5286K->3792K(29696K), 0.0029419 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 7094K->728K(9216K)] 10174K->9960K(29696K), 0.0036162 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 7163K->664K(9216K)] 16395K->16040K(29696K), 0.0029889 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (Ergonomics) [PSYoungGen: 664K->0K(9216K)] [ParOldGen: 15376K->16007K(20480K)] 16040K->16007K(29696K), [Metaspace: 3443K->3443K(1056768K)], 0.0102271 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 6281K->0K(9216K)] [ParOldGen: 16007K->647K(20480K)] 22289K->647K(29696K), [Metaspace: 3443K->3443K(1056768K)], 0.0056336 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 9216K, used 7427K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 90% used [0x00000000ff600000,0x00000000ffd40c00,0x00000000ffe00000)
  from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
 ParOldGen       total 20480K, used 647K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)
  object space 20480K, 3% used [0x00000000fe200000,0x00000000fe2a1db0,0x00000000ff600000)
 Metaspace       used 3948K, capacity 4568K, committed 4864K, reserved 1056768K
  class space    used 434K, capacity 460K, committed 512K, reserved 1048576K

分析:heap大小30m,根据默认的1:2原则,young大小10m、turned大小20m;
再根据8:1:1原则,Eden大小8m,from大小1m,to大小1m。

       为什么第四次发生了full gc?这里的原因就在于Parallel Scavenge GC时的悲观策略,当在eden上分配内存失败时且对象的大小尚不需要直接在old上分配时,会触发YGC,代码片段如下:

void PSScavenge::invoke(){  
    ...   
    bool scavenge_was_done = PSScavenge::invoke_no_policy();  
    PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters();  
    if (UsePerfData)  
        counters->update_full_follows_scavenge(0);  
    if(!scavenge_was_done || policy->should_full_GC(heap->old_gen()->free_in_bytes())){  
        if(UsePerfData)  
            counters->update_full_follows_scavenge(full_follows_scavenge);  
            < GCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);   
        if (UseParallelOldGC){  
            PSParallelCompact::invoke_no_policy(false);  
        }else{  
            PSMarkSweep::invoke_no_policy(false);   
        }  
    }  
    ...  
}  
PSScavenge::invoke_no_policy{  
    ...   
    if(!should_attempt_scavenge()){  
        return false;  
    }  
    ...  
}  
bool PSScavenge::should_attempt_scavenge(){  
    ...  
    PSAdaptiveSizePolicy* policy = heap->size_policy();  
    size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes();  
    size_t promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes());  
    bool result = promotion_estimate < old_gen->free_in_bytes();  
    ...  
    return result;  
}  

代码来源

       在上面should_attempt_scavenge代码片段中,可以看到会比较之前YGC晋升到Old中的平均大小与当前新生代中已被使用的字节数大小,取更小的值与旧生代目前剩余空间大小对比,如更大,则返回false,就终止了YGC的执行了,当返回false时,PSScavenge::invoke就将触发Full GC了。
在PSScavenge:invoke中还有一个条件为:policy->should_full_GC(heap->old_gen()->free_in_bytes(),来看看这段代码片段:

bool PSAdaptiveSizePolicy::should_full_GC(size_t old_free_in_bytes){  
    bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes;  
    ...  
    return result;  
}  


       可看到,这段代码检查的也是之前YGC时晋升到old的平均大小是否大于了旧生代的剩余空间,如大于,则触发full gc。
总结上面分析的策略,可以看到采用Parallel GC的情况下,当YGC触发时,会有两个检查:
1、在YGC执行前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 旧生代剩余空间大小 ? 不执行YGC,直接执行Full GC : 执行YGC;
2、在YGC执行后,平均晋升到old的大小 > 旧生代剩余空间大小 ? 触发Full GC : 什么都不做。

 

按照这样的说明,再来看看上面代码的执行过程中eden和old大小的变化状况:
代码 eden old YGC FGC
第一次循环 3 0 0 0
第二次循环 6 0 0 0
第三次循环 3 6 1 0
第四次循环 6 6 1 0
第五次循环 3 12 2 0
第六次循环 6 12 2 0
第七次循环 3 18 3 1
第八次循环 6 18 3 1
第九次循环 6  0 3 2

       在第7次循环时,YGC后旧生代剩余空间为2m,而之前平均晋级到old的对象大小为6m,因此在YGC后会触发一次FGC。
       而第9次循环时,在YGC执行前,此时新生代已使用的大小为6m,之前晋级到old的平均大小为6m,这两者去最小值为6m,这个值已大于old的剩余空间,因此就不执行YGC,直接执行FGC了。

       Sun JDK之所以要有悲观策略,我猜想理由是程序最终是会以一个较为稳态的状况执行的,此时每次YGC后晋升到old的对象大小应该是差不多的,在YGC时做好检查,避免等YGC后晋升到Old的对象导致old空间不足,因此还不如干脆就直接执行FGC,正因为悲观策略的存在,大家有些时候可能会看到old空间没满但full gc执行的状况。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值