簡書 占小狼
轉(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);
}