zoukankan      html  css  js  c++  java
  • GC悲观策略之Parallel GC

    先来看段代码:

    import java.util.ArrayList;
    import java.util.List;
    
    /**
    * -Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC
    * @author liuxiao
    *
    */
    public class Test1 {
    
      public static void main(String[] args) throws InterruptedException {
        List<Object> caches=new ArrayList<Object>(); 
        for(int i=0;i<7;i++){ 
          caches.add(new byte[1024*1024*3]); 
        } 
        caches.clear(); 
        for(int i=0;i<2;i++){ 
          caches.add(new byte[1024*1024*3]); 
        } 
        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 [PSYoungGen: 7470K->852K(9216K)] 7470K->6996K(29696K), 0.0765494 secs] [Times: user=0.03 sys=0.02, real=0.08 secs] 
    [GC [PSYoungGen: 7091K->772K(9216K)] 13235K->13060K(29696K), 0.0320251 secs] [Times: user=0.05 sys=0.00, real=0.03 secs] 
    [GC [PSYoungGen: 7062K->756K(9216K)] 19350K->19196K(29696K), 0.0111223 secs] [Times: user=0.03 sys=0.03, real=0.01 secs] 
    [Full GC [PSYoungGen: 756K->0K(9216K)] [ParOldGen: 18440K->19142K(20480K)] 19196K->19142K(29696K) [PSPermGen: 2665K->2664K(21504K)], 0.0318487 secs] [Times: user=0.08 sys=0.00, real=0.03 secs] 
    [Full GC [PSYoungGen: 6185K->0K(9216K)] [ParOldGen: 19142K->3782K(20480K)] 25327K->3782K(29696K) [PSPermGen: 2664K->2664K(21504K)], 0.0168417 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
    Heap
    PSYoungGen total 9216K, used 3263K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
    eden space 8192K, 39% used [0x00000000ff600000,0x00000000ff92fc28,0x00000000ffe00000)
    from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)
    to space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)
    ParOldGen total 20480K, used 3782K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)
    object space 20480K, 18% used [0x00000000fe200000,0x00000000fe5b1aa0,0x00000000ff600000)
    PSPermGen total 21504K, used 2671K [0x00000000f9000000, 0x00000000fa500000, 0x00000000fe200000)
    object space 21504K, 12% used [0x00000000f9000000,0x00000000f929bc00,0x00000000fa500000)

    分析: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大小的变化状况:

    在第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执行的状况。

    转:https://blog.csdn.net/liuxiao723846/article/details/72808495/

  • 相关阅读:
    读后感之—寒门学子重要选择-程序员
    架构中的分而治之
    如何从码农进化到项目管理者
    饿了么架构
    简单理解支付宝和蚂蚁花呗的架构
    架构小谈之美团外卖
    漫谈架构总结之1500
    平台基本信息项目目标文档
    第六学期每周总结-第三周
    质量管理之可用性战术分析
  • 原文地址:https://www.cnblogs.com/Noctis/p/12870802.html
Copyright © 2011-2022 走看看