關(guān)于CMS垃圾收集算法的一些疑惑

簡書 占小狼
轉(zhuǎn)載請注明原創(chuàng)出處袱蜡,謝謝漫仆!

對于CMS垃圾收集算法,一直有一些疑惑:
1职辅、cms gc 和 full gc 有什么區(qū)別 ?
2、cms gc 和 full gc 如何觸發(fā)的 司蔬?
3、什么場景下會發(fā)生 concurrent model failure 姨蝴?
4俊啼、full gc 每次都會進行compact么?
5左医、...(如果有疑惑繼續(xù)更新)

雖然CMS算法已經(jīng)被遺棄了授帕,但考慮到目前還有很大一部分應(yīng)用跑在該算法之下,是時候讀一遍源碼來加深理解了浮梢,不過最近得了一種一看源碼就頭疼的病跛十,所以這部分源碼斷斷續(xù)續(xù)看了好幾天,然后趁這個機會好好的梳理一下秕硝,如果期間存在問題芥映,歡迎指出。

cms gc 狀態(tài)

當(dāng)觸發(fā) cms gc 對老年代進行垃圾收集時,算法中會使用_collectorState變量記錄執(zhí)行狀態(tài)屏轰,整個周期分成以下幾個狀態(tài):

  • Idling:一次 cms gc 生命周期的初始化狀態(tài)颊郎。
  • InitialMarking:根據(jù) gc roots,標(biāo)記出直接可達的活躍對象霎苗,這個過程需要stw的姆吭。
  • Marking:根據(jù) InitialMarking 階段標(biāo)記出的活躍對象,并發(fā)迭代遍歷所有的活躍對象唁盏,這個過程可以和用戶線程并發(fā)執(zhí)行内狸。
  • Precleaning:并發(fā)預(yù)清理。
  • AbortablePreclean:因為某些原因終止預(yù)清理厘擂。
  • FinalMarking:由于marking階段是和用戶線程并發(fā)執(zhí)行的昆淡,該過程中可能有用戶線程修改某些活躍對象的字段,指向了一個非標(biāo)記過的對象刽严,在這個階段需要重新標(biāo)記出這些遺漏的對象昂灵,防止在下一階段被清理掉,這個過程也是需要stw的舞萄。
  • Sweeping:并發(fā)清理掉未標(biāo)記的對象眨补。
  • Resizing:如果有需要,重新調(diào)整堆大小倒脓。
  • Resetting:重置數(shù)據(jù)撑螺,為下一次的 cms gc 做準(zhǔn)備。

cms gc 和 full gc 的區(qū)別

CMS算法中實現(xiàn)了cms gc 和 full gc崎弃,姑且這么認(rèn)為吧甘晤,算法實現(xiàn)都位于文件concurrentMarkSweepGeneration.cpp中。

cms gc 通過一個后臺線程觸發(fā)饲做,觸發(fā)機制是默認(rèn)每隔2秒判斷一下當(dāng)前老年代的內(nèi)存使用率是否達到閾值线婚,當(dāng)然具體的觸發(fā)條件沒有這么簡單,如果是則觸發(fā)一次cms gc艇炎,在該過程中只會標(biāo)記出存活對象酌伊,然后清除死亡對象,期間會產(chǎn)生碎片空間缀踪。

full gc 是通過 vm thread 執(zhí)行的居砖,整個過程是 stop-the-world,在該過程中會判斷當(dāng)前 gc 是否需要進行compact驴娃,即把存活對象移動到內(nèi)存的一端奏候,可以有效的消除cms gc產(chǎn)生的碎片空間。

cms gc 如何觸發(fā)

對于 cms gc 來說唇敞,觸發(fā)條件很簡單蔗草,實現(xiàn)位于 ConcurrentMarkSweepThread 類中咒彤,相當(dāng)于Java 中的Thread,該線程隨著堆一起初始化咒精,在該類的 run 方法中有這么一段邏輯:

while (!_should_terminate) {
    sleepBeforeNextCycle();
    if (_should_terminate) break;
    GCCause::Cause cause = _collector->_full_gc_requested ?
      _collector->_full_gc_cause : GCCause::_cms_concurrent_mark;
    _collector->collect_in_background(false, cause);
}

sleepBeforeNextCycle()保證了最晚每 2 秒(-XX:CMSWaitDuration)進行一次判斷镶柱,實現(xiàn)如下:

void ConcurrentMarkSweepThread::sleepBeforeNextCycle() {
  while (!_should_terminate) {
    if (CMSIncrementalMode) {
      icms_wait();
      return;
    } else {
      // Wait until the next synchronous GC, a concurrent full gc
      // request or a timeout, whichever is earlier.
      wait_on_cms_lock(CMSWaitDuration);
    }
    // Check if we should start a CMS collection cycle
    if (_collector->shouldConcurrentCollect()) {
      return;
    }
    // .. collection criterion not yet met, let's go back
    // and wait some more
  }
}

其中shouldConcurrentCollect()方法決定了是否可以觸發(fā)本次 cms gc,分為以下幾種情況:

1模叙、如果_full_gc_requested為真歇拆,說明有明確的需求要進行g(shù)c,比如調(diào)用System.gc();

2范咨、CMS 默認(rèn)采用 jvm 運行時的統(tǒng)計數(shù)據(jù)判斷是否需要觸發(fā) cms gc故觅,如果需要根據(jù) CMSInitiatingOccupancyFraction 的值進行判斷,需要設(shè)置參數(shù)-XX:+UseCMSInitiatingOccupancyOnly

3渠啊、如果開啟了UseCMSInitiatingOccupancyOnly參數(shù)输吏,判斷當(dāng)前老年代使用率是否大于閾值谆吴,則觸發(fā) cms gc嗅战,該閾值可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction進行設(shè)置,如果沒有設(shè)置火诸,默認(rèn)為92%灭返;

4盗迟、如果之前的 ygc 失敗過,或則下次新生代執(zhí)行 ygc 可能失敗熙含,這兩種情況下都需要觸發(fā) cms gc;

5艇纺、CMS 默認(rèn)不會對永久代進行垃圾收集怎静,如果希望對永久代進行垃圾收集,需要設(shè)置參數(shù)-XX:+CMSClassUnloadingEnabled黔衡,如果開啟了CMSClassUnloadingEnabled蚓聘,根據(jù)永久帶的內(nèi)存使用率判斷是否觸發(fā) cms gc;

6盟劫、...還有一些其它情況

如果有上述幾種情況夜牡,說明需要執(zhí)行一次 cms gc,通過調(diào)用_collector->collect_in_background(false, cause) 進行觸發(fā)侣签,注意這個方法名中的in_background

full gc 如何觸發(fā)

觸發(fā) full gc 的主要原因是在eden區(qū)為對象或TLAB分配內(nèi)存失敗塘装,導(dǎo)致一次 ygc,在 GenCollectorPolicy 類的satisfy_failed_allocation()方法中有這么一段邏輯:

if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) {
    // Do an incremental collection.
    gch->do_collection(false            /* full */,
                       false            /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  } else {
    if (Verbose && PrintGCDetails) {
      gclog_or_tty->print(" :: Trying full because partial may fail :: ");
    }
    // Try a full collection; see delta for bug id 6266275
    // for the original code and why this has been simplified
    // with from-space allocation criteria modified and
    // such allocation moved out of the safepoint path.
    gch->do_collection(true             /* full */,
                       false            /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }

該方法是由 vm thread 執(zhí)行的影所,整個過程都是 stop-the-world蹦肴,如果當(dāng)前incremental_collection_will_fail方法返回 false,則會放棄本次的 ygc猴娩,直接觸發(fā)一次 full gc阴幌,incremental_collection_will_fail實現(xiàn)如下:

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());
  }

其中參數(shù) consult_young 為 false勺阐,如果incremental_collection_failed()返回 true,會導(dǎo)致執(zhí)行很慢很慢很慢的full gc矛双,如果上一次 ygc 過程中發(fā)生 promotion failure 時渊抽,會設(shè)置 _incremental_collection_failed為 true,即方法incremental_collection_failed()返回 true议忽,相當(dāng)于觸發(fā)了 full gc懒闷。

其實不管執(zhí)行 ygc 還是 full gc,都是執(zhí)行 GenCollectedHeap 的do_collection()方法徙瓶,最終執(zhí)行CMS算法的 full gc 實現(xiàn)位于CMSCollector::collect()方法中毛雇,當(dāng)然了,執(zhí)行 full gc 的邏輯和 cms gc 不是同一條路徑侦镇,只是實現(xiàn)在同一個文件不同方法中灵疮,而且 full gc 是單線程的,完全 stw壳繁,而cms gc 是多線程震捣,部分過程是stw的。

還有一種情況是闹炉,當(dāng)發(fā)生ygc之后蒿赢,還是沒有足夠的內(nèi)存進行分配,這時會繼續(xù)觸發(fā) full gc渣触,實現(xiàn)如下:

// If we reach this point, we're really out of memory. Try every trick
  // we can to reclaim memory. Force collection of soft references. Force
  // a complete compaction of the heap. Any additional methods for finding
  // free memory should be here, especially if they are expensive. If this
  // attempt fails, an OOM exception will be thrown.
  {
    IntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted

    gch->do_collection(true             /* full */,
                       true             /* clear_all_soft_refs */,
                       size             /* size */,
                       is_tlab          /* is_tlab */,
                       number_of_generations() - 1 /* max_level */);
  }

concurrent model failure羡棵?

在CMS中,full gc 也叫 The foreground collector嗅钻,對應(yīng)的 cms gc 叫 The background collector皂冰,在真正執(zhí)行 full gc 之前會判斷一下 cms gc 的執(zhí)行狀態(tài),如果 cms gc 正處于執(zhí)行狀態(tài)养篓,調(diào)用report_concurrent_mode_interruption()方法秃流,通知事件 concurrent mode failure,具體實現(xiàn)如下:

CollectorState first_state = _collectorState;
if (first_state > Idling) {
    report_concurrent_mode_interruption();
}
// 
void CMSCollector::report_concurrent_mode_interruption() {
  if (is_external_interruption()) {
    if (PrintGCDetails) {
      gclog_or_tty->print(" (concurrent mode interrupted)");
    }
  } else {
    if (PrintGCDetails) {
      gclog_or_tty->print(" (concurrent mode failure)");
    }
    _gc_tracer_cm->report_concurrent_mode_failure();
  }
}

這里可以發(fā)現(xiàn)是 full gc 導(dǎo)致了concurrent mode failure柳弄,而不是因為concurrent mode failure 錯誤導(dǎo)致觸發(fā) full gc舶胀,真正觸發(fā) full gc 的原因可能是 ygc 時發(fā)生的promotion failure

其實這里還有concurrent mode interrupted碧注,這是由于外部因素觸發(fā)了 full gc嚣伐,比如執(zhí)行了System.gc(),導(dǎo)致了這個原因应闯。

full gc中的compact

每次觸發(fā) full gc纤控,會根據(jù)should_compact 標(biāo)識進行判斷是否需要執(zhí)行 compact ,判斷實現(xiàn)如下:

*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 */));

UseCMSCompactAtFullCollection默認(rèn)開啟碉纺,但是否要進行 compact船万,還得看后面的條件:
1刻撒、最近一次cms gc 以來發(fā)生 full gc 的次數(shù)_full_gcs_since_conc_gc(這個值每次執(zhí)行完 cms gc 的sweeping 階段就會設(shè)置為0)達到閾值CMSFullGCsBeforeCompaction 。(但是閾值默認(rèn)為0耿导,哪里有設(shè)置它的地方声怔,不會每次 full gc 都是compact吧?)
2舱呻、用戶強制執(zhí)行了gc醋火,如System.gc()
3箱吕、上一次 ygc 已經(jīng)失斀娌怠(發(fā)生了promotion failure),或預(yù)測下一次 ygc 不會成功茬高。

如果上述條件都不滿足兆旬,是否就一直不進行 compact,這樣碎片問題就得不到緩解了怎栽,幸好還有補救的機會丽猬,實現(xiàn)如下:

if (clear_all_soft_refs && !*should_compact) {
    // We are about to do a last ditch collection attempt
    // so it would normally make sense to do a compaction
    // to reclaim as much space as possible.
    if (CMSCompactWhenClearAllSoftRefs) {
      // Default: The rationale is that in this case either
      // we are past the final marking phase, in which case
      // we'd have to start over, or so little has been done
      // that there's little point in saving that work. Compaction
      // appears to be the sensible choice in either case.
      *should_compact = true;
    } else {
      // We have been asked to clear all soft refs, but not to
      // compact. Make sure that we aren't past the final checkpoint
      // phase, for that is where we process soft refs. If we are already
      // past that phase, we'll need to redo the refs discovery phase and
      // if necessary clear soft refs that weren't previously
      // cleared. We do so by remembering the phase in which
      // we came in, and if we are past the refs processing
      // phase, we'll choose to just redo the mark-sweep
      // collection from scratch.
      if (_collectorState > FinalMarking) {
        // We are past the refs processing phase;
        // start over and do a fresh synchronous CMS cycle
        _collectorState = Resetting; // skip to reset to start new cycle
        reset(false /* == !asynch */);
        *should_start_over = true;
      } // else we can continue a possibly ongoing current cycle
    }

普通的 full gc,參數(shù)clear_all_soft_refs為 false熏瞄,不會清理軟引用脚祟,如果在執(zhí)行完 full gc,空間還是不足的話强饮,會執(zhí)行一次徹底的 full gc由桌,嘗試清理所有的軟引用,想方設(shè)法的收集可用內(nèi)存邮丰,這種情況clear_all_soft_refs為 true沥寥,而且CMSCompactWhenClearAllSoftRefs默認(rèn)為 true,在垃圾收集完可以執(zhí)行一次compact柠座,如果真的走到了這一步,該好好的查查代碼了片橡,因為這次 gc 的暫停時間已經(jīng)很長很長很長了妈经。

根據(jù)對should_compact參數(shù)的判斷,執(zhí)行不同的算法進行 full gc捧书,實現(xiàn)如下:

if (should_compact) {
    // If the collection is being acquired from the background
    // collector, there may be references on the discovered
    // references lists that have NULL referents (being those
    // that were concurrently cleared by a mutator) or
    // that are no longer active (having been enqueued concurrently
    // by the mutator).
    // Scrub the list of those references because Mark-Sweep-Compact
    // code assumes referents are not NULL and that all discovered
    // Reference objects are active.
    ref_processor()->clean_up_discovered_references();

    if (first_state > Idling) {
      save_heap_summary();
    }

    do_compaction_work(clear_all_soft_refs);

    // Has the GC time limit been exceeded?
    DefNewGeneration* young_gen = _young_gen->as_DefNewGeneration();
    size_t max_eden_size = young_gen->max_capacity() -
                           young_gen->to()->capacity() -
                           young_gen->from()->capacity();
    GenCollectedHeap* gch = GenCollectedHeap::heap();
    GCCause::Cause gc_cause = gch->gc_cause();
    size_policy()->check_gc_overhead_limit(_young_gen->used(),
                                           young_gen->eden()->used(),
                                           _cmsGen->max_capacity(),
                                           max_eden_size,
                                           full,
                                           gc_cause,
                                           gch->collector_policy());
  } else {
    do_mark_sweep_work(clear_all_soft_refs, first_state,  should_start_over);
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吹泡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子经瓷,更是在濱河造成了極大的恐慌爆哑,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舆吮,死亡現(xiàn)場離奇詭異揭朝,居然都是意外死亡队贱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門潭袱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柱嫌,“玉大人,你說我怎么就攤上這事屯换”嗲穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵彤悔,是天一觀的道長嘉抓。 經(jīng)常有香客問我,道長晕窑,這世上最難降的妖魔是什么抑片? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮幕屹,結(jié)果婚禮上蓝丙,老公的妹妹穿的比我還像新娘。我一直安慰自己望拖,他們只是感情好渺尘,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著说敏,像睡著了一般鸥跟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盔沫,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天医咨,我揣著相機與錄音,去河邊找鬼架诞。 笑死拟淮,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谴忧。 我是一名探鬼主播很泊,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沾谓!你這毒婦竟也來了委造?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤均驶,失蹤者是張志新(化名)和其女友劉穎昏兆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妇穴,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡爬虱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年隶债,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饮潦。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡燃异,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出继蜡,到底是詐尸還是另有隱情回俐,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布稀并,位于F島的核電站仅颇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碘举。R本人自食惡果不足惜忘瓦,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望引颈。 院中可真熱鬧耕皮,春花似錦、人聲如沸蝙场。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽售滤。三九已至罚拟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間完箩,已是汗流浹背赐俗。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弊知,地道東北人阻逮。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像秩彤,于是被迫代替她去往敵國和親夺鲜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • CMS是老年代垃圾收集器呐舔,在收集過程中可以與用戶線程并發(fā)操作。它可以與Serial收集器和Parallel New...
    zhong0316閱讀 40,666評論 2 27
  • 目錄 一.背景 二.CMS垃圾收集器特性 三.CMS執(zhí)行步驟 四.CMS日志解釋(JDK1.8): 五.CMS參數(shù)...
    愛吃糖果閱讀 4,399評論 0 3
  • 目錄一慷蠕、概述二珊拼、如何判斷對象是否為垃圾三、回收算法四流炕、垃圾收集器 一澎现、概述 GC內(nèi)存回收的地方虛擬機棧,本地方法棧...
    我shi杰迷閱讀 284評論 0 0
  • 文主要介紹4種垃圾收集算法及8種垃圾收集器: 垃圾收集算法 1仅胞、標(biāo)記-清除算法(Mark-Sweep) “標(biāo)記-清...
    星可碼農(nóng)閱讀 908評論 0 0
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭剑辫,有人歡樂有人憂愁干旧,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評論 28 53