簡書 滌生。
轉(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 種情況:
- GC(包含 foreground collector 和 compact 的 Full GC)次數(shù)
- GCCause 是否是用戶請(qǐng)求式觸發(fā)導(dǎo)致的
- 增量 GC 是否可能會(huì)失?。ū^策略)
- 是否清理所有 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)的可用性影響比較大。