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

簡書 滌生
轉(zhuǎn)載請(qǐng)注明原創(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)很明顯-低延時(shí),但最大的缺點(diǎn)是存在碎片,內(nèi)存空間利用率低。因此,CMS 為了解決這個(gè)問題,在每次進(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 在什么情況下會(huì)進(jìn)行 compact 的 Full GC。

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

何時(shí)會(huì)進(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);
}

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

  1. GC(包含 foreground collector 和 compact 的 Full GC)次數(shù)
  2. GCCause 是否是用戶請(qǐng)求式觸發(fā)導(dǎo)致的
  3. 增量 GC 是否可能會(huì)失?。ū^策略)
  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 是否是用戶請(qǐng)求式觸發(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);
  }

用戶請(qǐng)求式觸發(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 都會(huì)進(jìn)行一次壓縮式的 Full GC。

3. 增量 GC 是否可能會(huì)失?。ū^策略)

  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 是否會(huì)失敗。如果 Young GC 已經(jīng)失敗或者可能會(huì)失敗,CMS 就認(rèn)為可能存在碎片導(dǎo)致的,需要進(jìn)行一次壓縮式的 Full GC。

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

“!get_gen(0)->collection_attempt_is_safe()” 指的是 Young Gen 存活對(duì)象晉升是否可能會(huì)失敗。
通過判斷當(dāng)前 Old Gen 剩余的空間大小是否足夠容納 Young GC 晉升的對(duì)象大小。
Young GC 到底要晉升多少是無法提前知道的,因此,這里通過統(tǒng)計(jì)平均每次 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)存不夠的時(shí)候,GC 會(huì)回收相關(guān)對(duì)象內(nèi)存。這里說的就是需要回收所有軟引用的情況,在配置了 CMSCompactWhenClearAllSoftRefs 參數(shù)的情況下,會(huì)進(jìn)行一次壓縮式的 Full GC。

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

總結(jié)

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,527評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,656評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,853評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,103評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,487評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,815評(píng)論 2 372