圖解 CMS 垃圾回收機(jī)制,你值得擁有

什么是CMS

CMS全稱?Concurrent Mark Sweep描滔,是一款并發(fā)的、使用標(biāo)記-清除算法的垃圾回收器踪古,

如果老年代使用CMS垃圾回收器含长,需要添加虛擬機(jī)參數(shù)-”XX:+UseConcMarkSweepGC”。

使用場(chǎng)景:

GC過程短暫停伏穆,適合對(duì)時(shí)延要求較高的服務(wù)拘泞,用戶線程不允許長時(shí)間的停頓。

缺點(diǎn):

服務(wù)長時(shí)間運(yùn)行枕扫,造成嚴(yán)重的內(nèi)存碎片化陪腌。

另外,算法實(shí)現(xiàn)比較復(fù)雜(如果也算缺點(diǎn)的話)

實(shí)現(xiàn)機(jī)制

根據(jù)GC的觸發(fā)機(jī)制分為:周期性O(shè)ld GC(被動(dòng))和主動(dòng)Old GC

個(gè)人理解,實(shí)在不知道怎么分才好诗鸭。

周期性O(shè)ld GC

周期性O(shè)ld GC染簇,執(zhí)行的邏輯也叫Background Collect,對(duì)老年代進(jìn)行回收强岸,在GC日志中比較常見锻弓,由后臺(tái)線程ConcurrentMarkSweepThread循環(huán)判斷(默認(rèn)2s)是否需要觸發(fā)。

觸發(fā)條件

1蝌箍、如果沒有設(shè)置-XX:+UseCMSInitiatingOccupancyOnly青灼,虛擬機(jī)會(huì)根據(jù)收集的數(shù)據(jù)決定是否觸發(fā)(建議線上環(huán)境帶上這個(gè)參數(shù),不然會(huì)加大問題排查的難度)妓盲。

2杂拨、老年代使用率達(dá)到閾值?CMSInitiatingOccupancyFraction,默認(rèn)92%本橙。

3扳躬、永久代的使用率達(dá)到閾值?CMSInitiatingPermOccupancyFraction,默認(rèn)92%甚亭,前提是開啟?CMSClassUnloadingEnabled贷币。

4、新生代的晉升擔(dān)保失敗亏狰。

晉升擔(dān)保失敗

老年代是否有足夠的空間來容納全部的新生代對(duì)象或歷史平均晉升到老年代的對(duì)象役纹,如果不夠的話,就提早進(jìn)行一次老年代的回收暇唾,防止下次進(jìn)行YGC的時(shí)候發(fā)生晉升失敗促脉。

周期性O(shè)ld GC過程

當(dāng)條件滿足時(shí),采用“標(biāo)記-清理”算法對(duì)老年代進(jìn)行回收策州,過程可以說很簡單瘸味,標(biāo)記出存活對(duì)象,清理掉垃圾對(duì)象够挂,但是為了實(shí)現(xiàn)整個(gè)過程的低延遲旁仿,實(shí)際算法遠(yuǎn)遠(yuǎn)沒這么簡單,整個(gè)過程分為如下幾個(gè)部分:

對(duì)象在標(biāo)記過程中孽糖,根據(jù)標(biāo)記情況枯冈,分成三類:

白色對(duì)象,表示自身未被標(biāo)記办悟;

灰色對(duì)象尘奏,表示自身被標(biāo)記,但內(nèi)部引用未被處理病蛉;

黑色對(duì)象炫加,表示自身被標(biāo)記瑰煎,內(nèi)部引用都被處理;

假設(shè)發(fā)生Background Collect時(shí)琢感,Java堆的對(duì)象分布如下:

1丢间、InitialMarking(初始化標(biāo)記探熔,整個(gè)過程STW)

該階段單線程執(zhí)行驹针,主要分分為兩步:

標(biāo)記GC Roots可達(dá)的老年代對(duì)象;

遍歷新生代對(duì)象诀艰,標(biāo)記可達(dá)的老年代對(duì)象柬甥;

該過程結(jié)束后,對(duì)象分布如下:

2其垄、Marking(并發(fā)標(biāo)記)

該階段GC線程和應(yīng)用線程并發(fā)執(zhí)行苛蒲,遍歷InitialMarking階段標(biāo)記出來的存活對(duì)象,然后繼續(xù)遞歸標(biāo)記這些對(duì)象可達(dá)的對(duì)象绿满。

因?yàn)樵撾A段并發(fā)執(zhí)行的臂外,在運(yùn)行期間可能發(fā)生新生代的對(duì)象晉升到老年代、或者是直接在老年代分配對(duì)象喇颁、或者更新老年代對(duì)象的引用關(guān)系等等漏健,對(duì)于這些對(duì)象,都是需要進(jìn)行重新標(biāo)記的橘霎,否則有些對(duì)象就會(huì)被遺漏蔫浆,發(fā)生漏標(biāo)的情況。

為了提高重新標(biāo)記的效率姐叁,該階段會(huì)把上述對(duì)象所在的Card標(biāo)識(shí)為Dirty瓦盛,后續(xù)只需掃描這些Dirty Card的對(duì)象,避免掃描整個(gè)老年代外潜。

3原环、Precleaning(預(yù)清理)

通過參數(shù)CMSPrecleaningEnabled選擇關(guān)閉該階段,默認(rèn)啟用处窥,主要做兩件事情:

處理新生代已經(jīng)發(fā)現(xiàn)的引用嘱吗,比如在并發(fā)階段,在Eden區(qū)中分配了一個(gè)A對(duì)象碧库,A對(duì)象引用了一個(gè)老年代對(duì)象B(這個(gè)B之前沒有被標(biāo)記)柜与,在這個(gè)階段就會(huì)標(biāo)記對(duì)象B為活躍對(duì)象。

在并發(fā)標(biāo)記階段嵌灰,如果老年代中有對(duì)象內(nèi)部引用發(fā)生變化弄匕,會(huì)把所在的Card標(biāo)記為Dirty(其實(shí)這里并非使用CardTable,而是一個(gè)類似的數(shù)據(jù)結(jié)構(gòu)沽瞭,叫ModUnionTalble)迁匠,通過掃描這些Table,重新標(biāo)記那些在并發(fā)標(biāo)記階段引用被更新的對(duì)象(晉升到老年代的對(duì)象、原本就在老年代的對(duì)象)

4城丧、AbortablePreclean(可中斷的預(yù)清理)

該階段發(fā)生的前提是延曙,新生代Eden區(qū)的內(nèi)存使用量大于參數(shù)CMSScheduleRemarkEdenSizeThreshold?默認(rèn)是2M,如果新生代的對(duì)象太少亡哄,就沒有必要執(zhí)行該階段枝缔,直接執(zhí)行重新標(biāo)記階段。

為什么需要這個(gè)階段蚊惯,存在的價(jià)值是什么愿卸?

因?yàn)镃MS GC的終極目標(biāo)是降低垃圾回收時(shí)的暫停時(shí)間,所以在該階段要盡最大的努力去處理那些在并發(fā)階段被應(yīng)用線程更新的老年代對(duì)象截型,這樣在暫停的重新標(biāo)記階段就可以少處理一些趴荸,暫停時(shí)間也會(huì)相應(yīng)的降低。

在該階段宦焦,主要循環(huán)的做兩件事:

處理 From 和 To 區(qū)的對(duì)象发钝,標(biāo)記可達(dá)的老年代對(duì)象

和上一個(gè)階段一樣,掃描處理Dirty Card中的對(duì)象

當(dāng)然了波闹,這個(gè)邏輯不會(huì)一直循環(huán)下去酝豪,打斷這個(gè)循環(huán)的條件有三個(gè):

可以設(shè)置最多循環(huán)的次數(shù)?CMSMaxAbortablePrecleanLoops,默認(rèn)是0舔痪,意思沒有循環(huán)次數(shù)的限制寓调。

如果執(zhí)行這個(gè)邏輯的時(shí)間達(dá)到了閾值CMSMaxAbortablePrecleanTime,默認(rèn)是5s锄码,會(huì)退出循環(huán)夺英。

如果新生代Eden區(qū)的內(nèi)存使用率達(dá)到了閾值CMSScheduleRemarkEdenPenetration,默認(rèn)50%滋捶,會(huì)退出循環(huán)痛悯。(這個(gè)條件能夠成立的前提是,在進(jìn)行Precleaning時(shí)重窟,Eden區(qū)的使用率小于十分之一)

如果在循環(huán)退出之前载萌,發(fā)生了一次YGC,對(duì)于后面的Remark階段來說巡扇,大大減輕了掃描年輕代的負(fù)擔(dān)扭仁,但是發(fā)生YGC并非人為控制,所以只能祈禱這5s內(nèi)可以來一次YGC厅翔。


...

1678.150: [CMS-concurrent-preclean-start]

1678.186: [CMS-concurrent-preclean: 0.044/0.055secs]

1678.186: [CMS-concurrent-abortable-preclean-start]

1678.365: [GC 1678.465: [ParNew: 2080530K->1464K(2044544K), 0.0127340secs]

1389293K->306572K(2093120K),

0.0167509secs]

1680.093: [CMS-concurrent-abortable-preclean: 1.052/1.907secs]?

....

在上面GC日志中乖坠,1678.186啟動(dòng)了AbortablePreclean階段,在隨后不到2s就發(fā)生了一次YGC刀闷。

5熊泵、FinalMarking(并發(fā)重新標(biāo)記仰迁,STW過程)

該階段并發(fā)執(zhí)行,在之前的并行階段(GC線程和應(yīng)用線程同時(shí)執(zhí)行顽分,好比你媽在打掃房間徐许,你還在扔紙屑),可能產(chǎn)生新的引用關(guān)系如下:

老年代的新對(duì)象被GC Roots引用

老年代的未標(biāo)記對(duì)象被新生代對(duì)象引用

老年代已標(biāo)記的對(duì)象增加新引用指向老年代其它對(duì)象

新生代對(duì)象指向老年代引用被刪除

也許還有其它情況..

上述對(duì)象中可能有一些已經(jīng)在Precleaning階段和AbortablePreclean階段被處理過卒蘸,但總存在沒來得及處理的雌隅,所以還有進(jìn)行如下的處理:

遍歷新生代對(duì)象,重新標(biāo)記

根據(jù)GC Roots悬秉,重新標(biāo)記

遍歷老年代的Dirty Card澄步,重新標(biāo)記冰蘑,這里的Dirty Card大部分已經(jīng)在clean階段處理過

在第一步驟中和泌,需要遍歷新生代的全部對(duì)象,如果新生代的使用率很高祠肥,需要遍歷處理的對(duì)象也很多武氓,這對(duì)于這個(gè)階段的總耗時(shí)來說,是個(gè)災(zāi)難(因?yàn)榭赡艽罅康膶?duì)象是暫時(shí)存活的仇箱,而且這些對(duì)象也可能引用大量的老年代對(duì)象县恕,造成很多應(yīng)該回收的老年代對(duì)象而沒有被回收,遍歷遞歸的次數(shù)也增加不少)剂桥,如果在AbortablePreclean階段中能夠恰好的發(fā)生一次YGC忠烛,這樣就可以避免掃描無效的對(duì)象。

如果在AbortablePreclean階段沒來得及執(zhí)行一次YGC权逗,怎么辦美尸?

CMS算法中提供了一個(gè)參數(shù):CMSScavengeBeforeRemark,默認(rèn)并沒有開啟斟薇,如果開啟該參數(shù)师坎,在執(zhí)行該階段之前,會(huì)強(qiáng)制觸發(fā)一次YGC堪滨,可以減少新生代對(duì)象的遍歷時(shí)間胯陋,回收的也更徹底一點(diǎn)。

不過袱箱,這種參數(shù)有利有弊遏乔,利是降低了Remark階段的停頓時(shí)間,弊的是在新生代對(duì)象很少的情況下也多了一次YGC发笔,最可憐的是在AbortablePreclean階段已經(jīng)發(fā)生了一次YGC盟萨,然后在該階段又傻傻的觸發(fā)一次。

所以利弊需要把握筐咧。

主動(dòng)Old GC

這個(gè)主動(dòng)Old GC的過程鸯旁,觸發(fā)條件比較苛刻:

YGC過程發(fā)生Promotion Failed噪矛,進(jìn)而對(duì)老年代進(jìn)行回收

比如執(zhí)行了System.gc(),前提是沒有參數(shù)ExplicitGCInvokesConcurrent

其它情況…

如果觸發(fā)了主動(dòng)Old GC铺罢,這時(shí)周期性O(shè)ld GC正在執(zhí)行艇挨,那么會(huì)奪過周期性O(shè)ld GC的執(zhí)行權(quán)(同一個(gè)時(shí)刻只能有一種在Old GC在運(yùn)行),并記錄 concurrent mode failure 或者 concurrent mode interrupted韭赘。

主動(dòng)GC開始時(shí)缩滨,需要判斷本次GC是否要對(duì)老年代的空間進(jìn)行Compact(因?yàn)殚L時(shí)間的周期性GC會(huì)造成大量的碎片空間),判斷邏輯實(shí)現(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 */));

在三種情況下會(huì)進(jìn)行壓縮:

其中參數(shù)UseCMSCompactAtFullCollection(默認(rèn)true)和?CMSFullGCsBeforeCompaction(默認(rèn)0)泉瞻,所以默認(rèn)每次的主動(dòng)GC都會(huì)對(duì)老年代的內(nèi)存空間進(jìn)行壓縮脉漏,就是把對(duì)象移動(dòng)到內(nèi)存的最左邊。

當(dāng)然了袖牙,比如執(zhí)行了System.gc()侧巨,前提是沒有參數(shù)ExplicitGCInvokesConcurrent,也會(huì)進(jìn)行壓縮鞭达。

如果新生代的晉升擔(dān)保會(huì)失敗司忱。

帶壓縮動(dòng)作的算法,稱為MSC畴蹭,標(biāo)記-清理-壓縮坦仍,采用單線程,全暫停的方式進(jìn)行垃圾收集叨襟,暫停時(shí)間很長很長…

那不帶壓縮動(dòng)作的算法是什么樣的呢繁扎?

不帶壓縮動(dòng)作的執(zhí)行邏輯叫Foreground Collect,整個(gè)過程相對(duì)周期性O(shè)ld GC來說糊闽,少了Precleaning和AbortablePreclean兩個(gè)階段梳玫,其它過程都差不多。

如果執(zhí)行System.gc()墓怀,而且添加了參數(shù)ExplicitGCInvokesConcurrent汽纠,這時(shí)并不屬于主動(dòng)GC,它會(huì)推進(jìn)周期性O(shè)ld GC的進(jìn)行傀履,比如剛剛執(zhí)行過一次虱朵,并不會(huì)等2s后檢查條件,而是立馬啟動(dòng)周期性O(shè)ld GC钓账。

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 854393687

群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用碴犬、高并發(fā)、高性能及分布式梆暮、Jvm性能調(diào)優(yōu)服协、Spring源碼,MyBatis啦粹,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來學(xué)習(xí)提升自己偿荷,不要再用"沒有時(shí)間“來掩飾自己思想上的懶惰窘游!趁年輕,使勁拼跳纳,給未來的自己一個(gè)交代忍饰!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寺庄,隨后出現(xiàn)的幾起案子艾蓝,更是在濱河造成了極大的恐慌,老刑警劉巖斗塘,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赢织,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡馍盟,警方通過查閱死者的電腦和手機(jī)于置,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朽合,“玉大人俱两,你說我怎么就攤上這事〔懿剑” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵休讳,是天一觀的道長讲婚。 經(jīng)常有香客問我,道長俊柔,這世上最難降的妖魔是什么筹麸? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮雏婶,結(jié)果婚禮上物赶,老公的妹妹穿的比我還像新娘。我一直安慰自己留晚,他們只是感情好酵紫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著错维,像睡著了一般奖地。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赋焕,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天参歹,我揣著相機(jī)與錄音,去河邊找鬼隆判。 笑死犬庇,一個(gè)胖子當(dāng)著我的面吹牛僧界,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播臭挽,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼捎泻,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了埋哟?” 一聲冷哼從身側(cè)響起笆豁,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赤赊,沒想到半個(gè)月后闯狱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡抛计,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年哄孤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吹截。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘦陈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出波俄,到底是詐尸還是另有隱情晨逝,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布懦铺,位于F島的核電站捉貌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏冬念。R本人自食惡果不足惜趁窃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望急前。 院中可真熱鬧醒陆,春花似錦、人聲如沸裆针。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽据块。三九已至码邻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間另假,已是汗流浹背像屋。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留边篮,地道東北人己莺。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓奏甫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凌受。 傳聞我的和親對(duì)象是個(gè)殘疾皇子阵子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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