溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

你不知道的CMS GC

發(fā)布時(shí)間:2020-06-29 00:24:31 來(lái)源:網(wǎng)絡(luò) 閱讀:606 作者:Java_老男孩 欄目:編程語(yǔ)言

在G1出來(lái)之前,CMS絕對(duì)是OLTP系統(tǒng)的標(biāo)配。即使G1出來(lái)幾年了,生產(chǎn)環(huán)境很多的JVM實(shí)例還是采用ParNew+CMS的組合。但是即使其得到這么廣泛的應(yīng)用,還是有很多同學(xué)對(duì)它有很深的誤解。本文主要對(duì)ParNew+CMS經(jīng)典組合下,觸發(fā)的幾種垃圾回收方式進(jìn)行幾個(gè)概念的糾正。

Backgroud

可能更多人只知道CMS,而不知道Backgroud CMS。事實(shí)上我們說(shuō)的CMS,即包含了5個(gè)階段的CMS,就是Background CMS,如下圖所示:

你不知道的CMS GC

說(shuō)明

  • 圖中初始化標(biāo)記階段是串行的,這是JDK7的行為。JDK8以后默認(rèn)是并行的,可以通過(guò)參數(shù)-XX:+CMSParallelInitialMarkEnabled控制。

  • 由圖可知,CMS還有兩個(gè)階段是完全STW(Stop The World)的,即初始化標(biāo)記和最終標(biāo)記(重新標(biāo)記)。

  • 其他階段都是并發(fā)的,所以CMS被稱為Concurrent Mark&Sweep,但是我認(rèn)為前面還需要加個(gè)Mostly才是最貼切,即CMS是一個(gè)Mostly Concurrent Mark and Sweep Garbage Collector,因?yàn)樗€沒辦法做到完全并發(fā)。

不只是CMS,就是G1,以及JDK11的ZGC都沒有做到完全的并發(fā)。就目前筆者了解到的所有GC中,只有Azul的C四是完全并發(fā)的。

為什么有個(gè)Background關(guān)鍵詞?我們都知道配置CMS垃圾回收的話,有兩個(gè)重要參數(shù):-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly,這兩個(gè)參數(shù)表示只有在Old區(qū)占了75%的內(nèi)存時(shí)才滿足觸發(fā)CMS的條件。注意這只是滿足觸發(fā)CMS GC的條件。至于什么時(shí)候真正觸發(fā)CMS GC,由一個(gè)后臺(tái)掃描線程決定。CMSThread默認(rèn)2秒鐘掃描一次,判斷是否需要觸發(fā)CMS,這個(gè)參數(shù)可以更改這個(gè)掃描時(shí)間間隔,例如-XX:CMSWaitDuration=5000,此外可以通過(guò)jstack日志看到這個(gè)線程:

"Concurrent?Mark-Sweep?GC?Thread"?os_prio=2?tid=0x000000001870f800?nid=0x0f4?waiting?on?condition

Foregroud

這個(gè)名詞第一次聽笨神說(shuō)的。當(dāng)然笨神也不是隨便自己捏造一個(gè)名詞出來(lái),這個(gè)名詞來(lái)自于openjdk源碼,參考concurrentMarkSweepGeneration.cpp

void CMSCollector::collect_in_foreground(bool clear_all_soft_refs, GCCause::Cause cause) {
    case Resizing: {
        // nothing to be done in this state. 即這個(gè)階段啥都沒做
        _collectorState = Resetting;
        break;
    }  
    case Precleaning:
        // 預(yù)清理啥都沒干
    case AbortablePreclean:
        // Elide(省略,取消的意思,相當(dāng)于這個(gè)階段也啥都沒做) the preclean phase
        _collectorState = FinalMarking;
        break;
    default:
        ShouldNotReachHere();
}

源碼比較多,我就不全部貼出來(lái)的,有興趣的同學(xué)可以自己下載源碼查看。

它發(fā)生的場(chǎng)景,比如業(yè)務(wù)線程請(qǐng)求分配內(nèi)存,但是內(nèi)存不夠了,于是可能觸發(fā)一次CMS GC,這個(gè)過(guò)程就必須要等待內(nèi)存分配成功后業(yè)務(wù)線程才能繼續(xù)往下面走,因此整個(gè)過(guò)程必須STW,所以這種CMS GC整個(gè)過(guò)程都是STW,但是為了提高效率,它并不是每個(gè)階段都會(huì)走的,只走其中一些階段,通過(guò)上面的源碼可知,這些省下來(lái)的階段主要是并行階段:Precleaning、AbortablePreclean,Resizing。但不管怎么說(shuō)如果走了類似foreground這種CMS GC,那么整個(gè)過(guò)程業(yè)務(wù)線程都是不可用的,效率會(huì)影響挺大。

foreground收集模式事實(shí)上就是發(fā)生了FullGC,由這段的分析可知FullGC相比CMS Backgroud ?collect模式差距還是非常大的。

如果觸發(fā)了FullGC,那就是ParNew+CMS組合最糟糕的情況。因?yàn)檫@個(gè)時(shí)候并發(fā)模式已經(jīng)搞不定了,而且整個(gè)過(guò)程單線程,完全STW,甚至可能還會(huì)壓縮堆(是否壓縮堆在后面的MSC段落說(shuō)明),真的不能再糟糕了!想象一下如果這時(shí)候業(yè)務(wù)量比較大,由于FullGC導(dǎo)致服務(wù)完全暫停幾秒鐘,甚至上10秒,對(duì)用戶體驗(yàn)影響得多大。

另外,別以為G1就好很多,G1的FullGC同樣是垃圾級(jí)別的存在:

The G1 garbage collector is designed to avoid full collections, but when the concurrent collections can't reclaim memory fast enough a fall back full GC will occur. The current implementation of the?full GC for G1 uses a single threaded mark-sweep-compact algorithm.

原文出自:http://openjdk.java.net/jeps/307

MSC

MSC的全稱是Mark Sweep Compact,即標(biāo)記-清理-壓縮,MSC是一種算法,請(qǐng)注意Compact,即它會(huì)壓縮整理堆,這一點(diǎn)很重要。

這是foreground CMS在特定情況下才會(huì)采用的一種垃圾回收算法。至于什么時(shí)候會(huì)采用MSC進(jìn)行壓縮呢,請(qǐng)看源碼,依然在concurrentMarkSweepGeneration.cpp中:

//a method used by foreground collection to determine what type of collection 
//(compacting or not, continuing or fresh)it should do.
void CMSCollector::decide_foreground_collection_type(){
  ... ... 
  *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 */));    
  ... ... 
}

由這段源碼可知,foreground收集模式下如果采用MSC算法的壓縮模式,那么在-XX:+UseCMSCompactAtFullCollection前提下有三種可能:

  1. 上一次CMS并發(fā)GC執(zhí)行過(guò)后,再執(zhí)行參數(shù)-XX:CMSFullGCsBeforeCompaction=0指定的Full GC次數(shù),0表示每次FullGC后都會(huì)壓縮,同時(shí)0也是默認(rèn)值;

  2. 調(diào)用了System.gc(),當(dāng)然這就要滿足-XX:-DisableExplicitGC

  3. 晉升擔(dān)保失敗,即預(yù)計(jì)Old區(qū)沒有足夠空間來(lái)容納下次YoungGC晉升的對(duì)象;

HOW?

碎片化問(wèn)題一直是CMS采用的標(biāo)記清理算法最讓人詬病的地方:Backgroud CMS采用的標(biāo)記清理算法會(huì)導(dǎo)致內(nèi)存碎片問(wèn)題,從而埋下發(fā)生FullGC導(dǎo)致長(zhǎng)時(shí)間STW的隱患

FullGC這么恐怖,有辦法緩解么,或者說(shuō)盡量避免它在白天,甚至業(yè)務(wù)高峰期出現(xiàn)?有!筆者給你分享一個(gè)歪門邪道,不記得是多少年前,在哪里道聽途說(shuō)才得到這個(gè)偏方的,而且據(jù)說(shuō)以前阿里的一些業(yè)務(wù)也用了這個(gè)偏方,不管是哪里得來(lái)的偏方,反正肯定有用的。這個(gè)偏方很簡(jiǎn)單:在業(yè)務(wù)最低峰期(比如大陸的很多業(yè)務(wù)可以選在凌晨2,3點(diǎn)夜深人靜的時(shí)候)強(qiáng)行觸發(fā)FullGC(需要結(jié)合參數(shù)-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0,這兩個(gè)參數(shù)默認(rèn)值就是這樣的,表示觸發(fā)FullGC時(shí)壓縮堆),從而優(yōu)化內(nèi)存碎片并壓縮堆,降低在業(yè)務(wù)高峰期發(fā)生FullGC的概率(只能降低,不能杜絕)。

可能還有一小部分同學(xué)連強(qiáng)行觸發(fā)FullGC都不知道,筆者好人做到底,送佛送到西:

# 沒有開啟-XX:+DisableExplicitGC的前提下調(diào)用System.gc()就會(huì)發(fā)生FullGC
System.gc();

或者通過(guò)jmap命令觸發(fā):
# jmap -histo:live pid

總結(jié)

按照慣例,最后來(lái)個(gè)總結(jié):

  • 正常情況下觸發(fā)Backgroud模式的CMS GC,這是并發(fā)模式收集,對(duì)業(yè)務(wù)影響很小,你好我好都好。

  • 當(dāng)并發(fā)模式搞不定了,就會(huì)退化成Foreground模式,這個(gè)回收過(guò)程業(yè)務(wù)線程是不可用的,這時(shí)候就觸發(fā)了FullGC。

  • 接下來(lái)根據(jù)是否滿足上面提到幾個(gè)條件決定是否采用MSC算法壓縮堆。

  • CMSFullGCsBeforeCompaction決定多少次FullGC后壓縮堆,具體配置多大,由你決定,但是不建議太大,否則在采用MSC算法壓縮堆之前,由于內(nèi)存碎片的問(wèn)題,導(dǎo)致出現(xiàn)promotion failure,總之這是trade-off。
向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI