JVM 源碼解讀之 CMS 何時會進(jìn)行 Full GC

簡書 滌生
轉(zhuǎn)載請注明原創(chuàng)出處,謝謝!
如果讀完覺得有收獲的話,歡迎點(diǎn)贊加關(guān)注。

前言

本文內(nèi)容是基于 JDK 8

在文章 JVM 源碼解讀之 CMS GC 觸發(fā)條件 中分析了 CMS GC 觸發(fā)的五類情況,并且提到 CMS GC 分為 foreground collector 和 background collector。
不管是 foreground collector 還是 background collector 使用的都是 mark-sweep 算法,分階段進(jìn)行標(biāo)記清理,優(yōu)點(diǎn)很明顯-低延時,但最大的缺點(diǎn)是存在碎片,內(nèi)存空間利用率低。因此,CMS 為了解決這個問題,在每次進(jìn)行 foreground collector 之前,判斷是否需要進(jìn)行一次壓縮式 GC。

此壓縮式 GC,CMS 使用的是跟 Serial Old GC 一樣的 LISP2 算法,其使用 mark-compact 來做 Full GC,一般稱之為 MSC(mark-sweep-compact),它收集的范圍是 Java 堆的 Young Gen 和 Old Gen,以及 metaspace(元空間)。

本文不涉及具體的收集過程,只分析 CMS 在什么情況下會進(jìn)行 compact 的 Full GC。

什么情況下會進(jìn)行一次壓縮式 Full GC 呢?

何時會進(jìn)行 FullGC?

下面這段代碼就是 CMS 進(jìn)行判斷是進(jìn)行 mark-sweep 的 foreground collector,還是進(jìn)行 mark-sweep-compact 的 Full GC。主要的判斷依據(jù)就是是否進(jìn)行壓縮,即代碼中的 should_compact。

// Check if we need to do a compaction, or if not, whether
// we need to start the mark-sweep from scratch.
bool should_compact    = false;
bool should_start_over = false;
decide_foreground_collection_type(clear_all_soft_refs,
    &should_compact, &should_start_over);
...
if (should_compact) {
    ...
    // mark-sweep-compact
    do_compaction_work(clear_all_soft_refs);
    ...
} else {
    // mark-sweep
    do_mark_sweep_work(clear_all_soft_refs, first_state,
      should_start_over);
}

接下來我們就來分析下在什么情況下會進(jìn)行 compact,
來看 decide_foreground_collection_type 函數(shù),主要分為 4 種情況:

  1. GC(包含 foreground collector 和 compact 的 Full GC)次數(shù)
  2. GCCause 是否是用戶請求式觸發(fā)導(dǎo)致的
  3. 增量 GC 是否可能會失?。ū^策略)
  4. 是否清理所有 SoftReference
void CMSCollector::decide_foreground_collection_type(
  bool clear_all_soft_refs, bool* should_compact,
  bool* should_start_over) {
  ...
  
  // 判斷是否壓縮的邏輯
  
  *should_compact =
    UseCMSCompactAtFullCollection &&
    ((_full_gcs_since_conc_gc >= CMSFullGCsBeforeCompaction) ||
     GCCause::is_user_requested_gc(gch->gc_cause()) ||
     gch->incremental_collection_will_fail(true /* consult_young */));
  *should_start_over = false;
  
  if (clear_all_soft_refs && !*should_compact) {
  
    if (CMSCompactWhenClearAllSoftRefs) {
      *should_compact = true;
    } else {
    
        if (_collectorState > FinalMarking) {
        _collectorState = Resetting; // skip to reset to start new cycle
        reset(false /* == !asynch */);
        *should_start_over = true;
      } 
    }
  }
}

接下來我們具體看每種情況

1. GC(包含 foreground collector 和 compact 的 Full GC)次數(shù)

// UseCMSCompactAtFullCollection 參數(shù)值默認(rèn)是 true
UseCMSCompactAtFullCollection &&
    ((_full_gcs_since_conc_gc >= CMSFullGCsBeforeCompaction)

這里說的 GC 次數(shù) _full_gcs_since_conc_gc,指的是從上次 background collector 后,foreground collector 和 compact 的 Full GC 的次數(shù),只要次數(shù)大于等于 CMSFullGCsBeforeCompaction 參數(shù)閾值,就表示可以進(jìn)行一次壓縮式的 Full GC。
(CMSFullGCsBeforeCompaction 參數(shù)默認(rèn)是 0,意味著默認(rèn)是要進(jìn)行壓縮式的 Full GC。)

2. GCCause 是否是用戶請求式觸發(fā)導(dǎo)致

 inline static bool is_user_requested_gc(GCCause::Cause cause) {
    return (cause == GCCause::_java_lang_system_gc ||
            cause == GCCause::_jvmti_force_gc);
  }

用戶請求式觸發(fā)導(dǎo)致的 GCCause 指的是 _java_lang_system_gc(即 System.gc())或者 _jvmti_force_gc(即 JVMTI 方式的強(qiáng)制 GC)
意味著只要是 System.gc(前提沒有配置 ExplicitGCInvokesConcurrent 參數(shù))調(diào)用或者 JVMTI 方式的強(qiáng)制 GC 都會進(jìn)行一次壓縮式的 Full GC。

3. 增量 GC 是否可能會失敗(悲觀策略)

  bool incremental_collection_will_fail(bool consult_young) {
    // Assumes a 2-generation system; the first disjunct remembers if an
    // incremental collection failed, even when we thought (second disjunct)
    // that it would not.
    assert(heap()->collector_policy()->is_two_generation_policy(),
           "the following definition may not be suitable for an n(>2)-generation system");
    return incremental_collection_failed() ||
           (consult_young && !get_gen(0)->collection_attempt_is_safe());
  }

JVM 源碼解讀之 CMS GC 觸發(fā)條件 文章中也提到了這塊內(nèi)容,
指的是兩代的 GC 體系中,主要指的是 Young GC 是否會失敗。如果 Young GC 已經(jīng)失敗或者可能會失敗,CMS 就認(rèn)為可能存在碎片導(dǎo)致的,需要進(jìn)行一次壓縮式的 Full GC。

“incremental_collection_failed()” 這里指的是 Young GC 已經(jīng)失敗,至于為什么會失敗一般是因?yàn)?Old Gen 沒有足夠的空間來容納晉升的對象,比如常見的 “promotion failed” 。

“!get_gen(0)->collection_attempt_is_safe()” 指的是 Young Gen 存活對象晉升是否可能會失敗。
通過判斷當(dāng)前 Old Gen 剩余的空間大小是否足夠容納 Young GC 晉升的對象大小。
Young GC 到底要晉升多少是無法提前知道的,因此,這里通過統(tǒng)計平均每次 Young GC 晉升的大小和當(dāng)前 Young GC 可能晉生的最大大小來進(jìn)行比較。

下面展示的就是 collection_attempt_is_safe 函數(shù)的代碼:

bool DefNewGeneration::collection_attempt_is_safe() {
  if (!to()->is_empty()) {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: to is not empty :: ");
    }
    return false;
  }
  if (_next_gen == NULL) {
    GenCollectedHeap* gch = GenCollectedHeap::heap();
    _next_gen = gch->next_gen(this);
  }
  return _next_gen->promotion_attempt_is_safe(used());
}

4. 是否清理所有 SoftReference

if (clear_all_soft_refs && !*should_compact) {
  
    if (CMSCompactWhenClearAllSoftRefs) {
      *should_compact = true;
    } 
    ...

SoftReference 軟引用,你應(yīng)該了解它的特性,一般是在內(nèi)存不夠的時候,GC 會回收相關(guān)對象內(nèi)存。這里說的就是需要回收所有軟引用的情況,在配置了 CMSCompactWhenClearAllSoftRefs 參數(shù)的情況下,會進(jìn)行一次壓縮式的 Full GC。

JDK 1.9 有變更:
徹底去掉了 CMS forground collector 的功能,也就是說除了 background collector,就是壓縮式的 Full GC。自然(UseCMSCompactAtFullCollection、CMSFullGCsBeforeCompaction 這兩個參數(shù)也已經(jīng)不在支持了。

總結(jié)

本文著重介紹了 CMS 在以下 4 種情況:

  • GC(包含 foreground collector 和 compact 的 Full GC)次數(shù)
  • GCCause 是否是用戶請求式觸發(fā)導(dǎo)致
  • 增量 GC 是否可能會失?。ū^策略)
  • 是否清理所有 SoftReference

會進(jìn)行壓縮式的 Full GC,并且詳細(xì)介紹了每種情況下的觸發(fā)條件。
我們在 GC 調(diào)優(yōu)時應(yīng)該盡可能的避免壓縮式的 Full GC,因?yàn)槠涫褂玫氖?Serial Old GC 類似算法,它是單線程對全堆以及 metaspace 進(jìn)行回收,STW 的時間會特別長,對業(yè)務(wù)系統(tǒng)的可用性影響比較大。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容