詳解CMS垃圾回收機制

  1. 什么是CMS厦凤?

    Concurrent Mark Sweep。

    看名字就知道育苟,CMS是一款并發(fā)较鼓、使用標記-清除算法的gc。

    CMS是針對老年代進行回收的GC违柏。

  2. CMS有什么用博烂?

    CMS以獲取最小停頓時間為目的。

    在一些對響應(yīng)時間有很高要求的應(yīng)用或網(wǎng)站中漱竖,用戶程序不能有長時間的停頓脖母,CMS 可以用于此場景。

  3. CMS如何執(zhí)行闲孤?

總體來說CMS的執(zhí)行過程可以分為以下幾個階段:

 3.1 初始標記(STW)

 3.2 并發(fā)標記

 3.3 并發(fā)預(yù)清理

 3.4 重標記(STW)

 3.5 并發(fā)清理

 3.6 重置

3.1 初始標記階段需要STW。

該階段進行可達性分析烤礁,標記GC ROOT能直接關(guān)聯(lián)到的對象讼积。

注意是直接關(guān)聯(lián)間接關(guān)聯(lián)的對象在下一階段標記。


3.2 并發(fā)標記階段是和用戶線程并發(fā)執(zhí)行的過程脚仔。

該階段進行GC ROOT TRACING勤众,在第一個階段被暫停的線程重新開始運行。

由前階段標記過的對象出發(fā)鲤脏,所有可到達的對象都在本階段中標記们颜。


3.3 并發(fā)預(yù)處理階段做的工作還是標記吕朵,與3.4的重標記功能相似。

既然相似為什么要有這一步窥突?

前面我們講過努溃,CMS是以獲取最短停頓時間為目的的GC。

重標記需要STW(Stop The World)阻问,因此重標記的工作盡可能多的在并發(fā)階段完成來減少STW的時間梧税。

此階段標記從新生代晉升的對象新分配到老年代的對象以及在并發(fā)階段被修改了的對象称近。


此階段比較復(fù)雜第队,從初學(xué)者容易忽略或者說不理解的地方拋出一個問題大家思考下

  • 如何確定老年代的對象是活著的?

答案很簡單刨秆,通過GC ROOT TRACING可到達的對象就是活著的凳谦。

繼續(xù)延伸,如果存在以下場景怎么辦:

image.png

老年代進行GC時如何確保上圖中Current Obj標記為活著的衡未?

(*確認新生代的對象是活著的也存在相同問題尸执,大家可以思考下,文章后面會給出答案*

答案是必須掃描新生代來確保眠屎。這也是為什么CMS雖然是老年代的gc剔交,但仍要掃描新生代的原因。(注意初始標記也會掃描新生代)

在CMS日志中我們可以清楚地看到掃描日志:

[GC[YG occupancy: 820 K (6528 K)]

[Rescan (parallel) , 0.0024157 secs]

[weak refs processing, 0.0000143 secs]

[scrub string table, 0.0000258 secs] 

[1 CMS-remark: 479379K(515960K)] 480200K(522488K), 0.0025249 secs] 

[Times: user=0.01 sys=0.00, real=0.00 secs]

Rescan階段(remark階段的一個子階段)會掃描新生代和老年代中的對象改衩。在日志中可以看到此階段標識為Rescan (parallel)岖常,說明此階段是并行進行的。

(看到這里葫督,如果你心中仍有疑問說明已經(jīng)入門了)

重點來了:全量的掃描新生代和老年代會不會很慢竭鞍?肯定會。

CMS號稱是停頓時間最短的GC橄镜,如此長的停頓時間肯定是不能接受的偎快。

如何解決呢?

你們先思考著洽胶。

必須要有一個能夠快速識別新生代和老年代活著的對象的機制晒夹。

先說新生代。

你應(yīng)該已經(jīng)知道姊氓,新生代垃圾回收完剩下的對象全是活著的丐怯,并且活著的對象很少。

如果在掃描新生代前進行一次Minor GC翔横,情況是不是就變得好很多读跷?

CMS 有兩個參數(shù):CMSScheduleRemarkEdenSizeThreshold、CMSScheduleRemarkEdenPenetration禾唁,默認值分別是2M效览、50%无切。兩個參數(shù)組合起來的意思是預(yù)清理后,eden空間使用超過2M時啟動可中斷的并發(fā)預(yù)清理(CMS-concurrent-abortable-preclean)丐枉,直到eden空間使用率達到50%時中斷哆键,進入remark階段。

如果能在可中止的預(yù)清理階段發(fā)生一次Minor GC,那就萬事大吉矛洞、天下太平了洼哎。

這里有一個小問題,可終止的預(yù)清理要執(zhí)行多長時間來保證發(fā)生一次Minor GC?

答案是沒法保證。道理很簡單沼本,因為垃圾回收是JVM自動調(diào)度的,什么時候進行GC我們控制不了噩峦。

但此階段總有一個執(zhí)行時間吧?是的抽兆。

CMS提供了一個參數(shù)CMSMaxAbortablePrecleanTime 识补,默認為5S。

只要到了5S辫红,不管發(fā)沒發(fā)生Minor GC凭涂,有沒有到CMSScheduleRemardEdenPenetration都會中止此階段,進入remark贴妻。

如果在5S內(nèi)還是沒有執(zhí)行Minor GC怎么辦切油?

CMS提供CMSScavengeBeforeRemark參數(shù),使remark前強制進行一次Minor GC名惩。

這樣做利弊都有澎胡。好的一面是減少了remark階段的停頓時間;壞的一面是Minor GC后緊跟著一個remark pause。如此一來娩鹉,停頓時間也比較久攻谁。

CMS日志如下:

7688.150: [CMS-concurrent-preclean-start]

7688.186: [CMS-concurrent-preclean: 0.034/0.035 secs]

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

7688.465: [GC 7688.465: [ParNew: 1040940K->1464K(1044544K), 0.0165840 secs] 1343593K->304365K(2093120K), 

0.0167509 secs]7690.093: [CMS-concurrent-abortable-preclean: 1.012/1.907 secs]  7690.095: [GC[YG occupancy: 522484 K (1044544 K)]

7690.095: [Rescan (parallel) , 0.3665541 secs]7690.462: [weak refs processing, 0.0003850 secs] [1 CMS-remark: 302901K(1048576K)] 825385K(2093120K), 0.3670690 secs]

7688.186啟動了可終止的預(yù)清理,在隨后的三秒內(nèi)啟動了Minor GC弯予,然后進入了Remark階段.

實際上為了減少remark階段的STW時間戚宦,預(yù)清理階段會盡可能多做一些事情來減少remark停頓時間。

remark的rescan階段是多線程的锈嫩,為了便于多線程掃描新生代受楼,預(yù)清理階段會將新生代分塊。

每個塊中存放著多個對象呼寸,這樣remark階段就不需要從頭開始識別每個對象的起始位置那槽。

多個線程的職責(zé)就很明確了,把分塊分配給多個線程等舔,很快就掃描完。

遺憾的是糟趾,這種辦法仍然是建立在發(fā)生了Minor GC的條件下慌植。

如果沒有發(fā)生Minor GC甚牲,top(下一個可以分配的地址空間)以下的所有空間被認為是一個塊(這個塊包含了新生代大部分內(nèi)容)。

這種塊對于remark階段并不會起到多少作用蝶柿,因此并行效率也會降低丈钙。


ok,新生代的機制講完了交汤,下面講講老年代雏赦。

老年代的機制與一個叫CARD TABLE的東西(這個東西其實就是個數(shù)組,數(shù)組中每個位置存的是一個byte)密不可分。

CMS將老年代的空間分成大小為512bytes的塊芙扎,card table中的每個元素對應(yīng)著一個塊星岗。

并發(fā)標記時,如果某個對象的引用發(fā)生了變化戒洼,就標記該對象所在的塊為 dirty card俏橘。

并發(fā)預(yù)清理階段就會重新掃描該塊,將該對象引用的對象標識為可達圈浇。

舉個例子:

并發(fā)標記時對象的狀態(tài):

image.png

但隨后current obj的引用發(fā)生了變化:

image.png

current obj所在的塊被標記為了dirty card寥掐。

隨后到了pre-cleaning階段,還記得該階段的任務(wù)之一就是標記這些在并發(fā)標記階段被修改了的對象么磷蜀?之后那些通過current obj變得可達的對象也被標記了召耘,變成下面這樣:

image.png

同時dirty card標志也被清除。

老年代的機制就是這樣褐隆。

不過card table還有其他作用污它。

還記得前面提到的那個問題么?進行Minor GC時,如果有老年代引用新生代妓灌,怎么識別轨蛤?

(有研究表明,在所有的引用中虫埂,老年代引用新生代這種場景不足1%.**原因大家可以自己分析下****)

當有老年代引用新生代祥山,對應(yīng)的card table被標識為相應(yīng)的值(card table中是一個byte,有八位掉伏,約定好每一位的含義就可區(qū)分哪個是引用新生代缝呕,哪個是并發(fā)標記階段修改過的)。

所以斧散,Minor GC通過掃描card table就可以很快的識別老年代引用新生代供常。

這里點一下,hotspot 虛擬機使用字節(jié)碼解釋器鸡捐、JIT編譯器栈暇、 write barrier維護 card table。

當字節(jié)碼解釋器或者JIT編譯器更新了引用箍镜,就會觸發(fā)write barrier操作card table.

再點一下源祈,由于card table的存在煎源,當老年代空間很大時會發(fā)生什么?(這里大家可以自由發(fā)揮想象

至此香缺,預(yù)清理階段的工作講完手销。


3.4 重標記(STW) 暫停所有用戶線程,重新掃描堆中的對象图张,進行可達性分析,標記活著的對象锋拖。

有了前面的基礎(chǔ),這個階段的工作量被大大減輕祸轮,停頓時間因此也會減少兽埃。

注意這個階段是多線程的。


3.5 并發(fā)清理倔撞。用戶線程被重新激活讲仰,同時清理那些無效的對象。


3.6 重置痪蝇。 CMS清除內(nèi)部狀態(tài)鄙陡,為下次回收做準備。


CMS執(zhí)行過程講完了躏啰,重點講解了并發(fā)預(yù)清理時的操作及CMS幾個關(guān)鍵參數(shù)趁矾。你們可以消化一下,消化完了可以休息一下给僵,因為事情還沒結(jié)束毫捣。

4. CMS有什么問題?

every coin has two sides ------高中英語作文我經(jīng)常用的一句話帝际。

在我看來蔓同,CMS這三個字母就隱含了問題所在并發(fā)+標記-清除算法 是問題的來源蹲诀。

先說并發(fā)

4.1并發(fā)意味著多線程搶占CPU資源斑粱,即GC線程與用戶線程搶占CPU。這可能會造成用戶線程執(zhí)行效率下降脯爪。

CMS默認的回收線程數(shù)是(CPU個數(shù)+3)/4则北。這個公式的意思是當CPU大于4個時,保證回收線程占用至少25%的CPU資源,這樣用戶線程占用75%的CPU痕慢,這是可以接受的尚揣。

但是,如果CPU資源很少掖举,比如只有兩個的時候怎么辦快骗?按照上面的公式,CMS會啟動1個GC線程。相當于GC線程占用了50%的CPU資源方篮,這就可能導(dǎo)致用戶程序的執(zhí)行速度忽然降低了50%思灌,50%已經(jīng)是很明顯的降低了。

這種場景怎么處理呢恭取?

我給的答案是可以不用考慮這種場景。現(xiàn)在的PC機中都至少有雙核處理器熄守,更別說大型的服務(wù)器了蜈垮。

CMS的解決方案是提供了一個 incremental mode(增量模式)。

在這種模式下裕照,進行并發(fā)標記攒发、清理時讓GC線程、用戶線程交替運行晋南,盡量減少GC線程獨占CPU資源的時間惠猿。

這會造成GC時間更長,但對用戶線程造成的影響就會少一些负间。

但實踐證明偶妖,這種模式下CMS的表現(xiàn)很一般,并沒有什么大的優(yōu)化政溃。

i-CMS已經(jīng)被聲明為“deprecated”趾访,不再提倡使用。

(https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector)

4.2 并發(fā)清理階段用戶線程還在運行董虱,這段時間就可能產(chǎn)生新的垃圾扼鞋,新的垃圾在此次GC無法清除,只能等到下次清理愤诱。這些垃圾有個專業(yè)名詞:浮動垃圾云头。

由于垃圾回收階段用戶線程仍在執(zhí)行,必需預(yù)留出內(nèi)存空間給用戶線程使用淫半。因此不能像其他回收器那樣溃槐,等到老年代滿了再進行GC。

CMS 提供了CMSInitiatingOccupancyFraction參數(shù)來設(shè)置老年代空間使用百分比,達到百分比就進行垃圾回收撮慨。

這個參數(shù)默認是92%竿痰,參數(shù)選擇需要看具體的應(yīng)用場景。

設(shè)置的太小會導(dǎo)致頻繁的CMS GC砌溺,產(chǎn)生大量的停頓影涉;反過來想,設(shè)置的太高會發(fā)生什么规伐?

假設(shè)現(xiàn)在設(shè)置為99%蟹倾,還剩1%的空間可以使用。

在并發(fā)清理階段,若用戶線程需要使用的空間大于1%鲜棠,就會產(chǎn)生Concurrent Mode Failure錯誤肌厨,意思就是說并發(fā)模式失敗。

這時豁陆,虛擬機就會啟動備案:使用Serial Old收集器重新對老年代進行垃圾回收.如此一來柑爸,停頓時間變得更長。

所以CMSInitiatingOccupancyFraction的設(shè)置要具體問題具體分析盒音。

網(wǎng)上有一些設(shè)置此參數(shù)的公式表鳍,個人認為不是很嚴謹(原因就是CMS另外一個問題導(dǎo)致的),因此不寫出來以免大家疑惑。

其實CMS有動態(tài)檢查機制祥诽。

CMS會根據(jù)歷史記錄譬圣,預(yù)測老年代還需要多久填滿及進行一次回收所需要的時間。

在老年代空間用完之前雄坪,CMS可以根據(jù)自己的預(yù)測自動執(zhí)行垃圾回收厘熟。

這個特性可以使用參數(shù)UseCMSInitiatingOccupancyOnly來關(guān)閉。

這里提個問題給讀者思考维哈,如果讓你設(shè)計绳姨,如何預(yù)測什么時候開始自動執(zhí)行

4.3 前兩個問題是由并發(fā)引起的笨农,接下來要說的問題就是由標記-清除算法引起的就缆。

使用標記-清除算法可能造成大量的空間碎片≮艘啵空間碎片過多竭宰,就會給大對象分配帶來麻煩。

往往老年代還有很大剩余空間份招,但無法找到足夠大的連續(xù)空間來分配當前對象,不得不觸發(fā)一次Full GC切揭。

CMS的解決方案是使用UseCMSCompactAtFullCollection參數(shù)(默認開啟),在頂不住要進行Full GC時開啟內(nèi)存碎片整理锁摔。

這個過程需要STW廓旬,碎片問題解決了,但停頓時間又變長了。

虛擬機還提供了另外一個參數(shù)CMSFullGCsBeforeCompaction谐腰,用于設(shè)置執(zhí)行多少次不壓縮的Full GC后孕豹,跟著來一次帶壓縮的(默認為0,每次進入Full GC時都進行碎片整理)十气。

延伸一個“foreground collector”的東西給大家励背,這個玩意在Java8中也聲明為deprecated。(https://bugs.openjdk.java.net/browse/JDK-8027132)

CMS存在的問題已經(jīng)講清楚砸西,大家消化下叶眉。


至此址儒,CMS相關(guān)內(nèi)容已經(jīng)講完。

總結(jié)一下:

CMS采用了多種方式盡可能降低GC的暫停時間,減少用戶程序停頓衅疙。

停頓時間降低的同時犧牲了CPU吞吐量 莲趣。

這是在停頓時間和性能間做出的取舍,可以簡單理解為"空間(性能)"換時間饱溢。

轉(zhuǎn)載地址:https://www.cnblogs.com/littleLord/p/5380624.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喧伞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子绩郎,更是在濱河造成了極大的恐慌絮识,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗽上,死亡現(xiàn)場離奇詭異锹杈,居然都是意外死亡级乐,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門屡律,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挪圾,“玉大人浅萧,你說我怎么就攤上這事≌芩迹” “怎么了洼畅?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長棚赔。 經(jīng)常有香客問我帝簇,道長,這世上最難降的妖魔是什么靠益? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任丧肴,我火速辦了婚禮,結(jié)果婚禮上胧后,老公的妹妹穿的比我還像新娘芋浮。我一直安慰自己,他們只是感情好壳快,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布纸巷。 她就那樣靜靜地躺著,像睡著了一般眶痰。 火紅的嫁衣襯著肌膚如雪瘤旨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天凛驮,我揣著相機與錄音裆站,去河邊找鬼。 笑死,一個胖子當著我的面吹牛宏胯,可吹牛的內(nèi)容都是我干的羽嫡。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼肩袍,長吁一口氣:“原來是場噩夢啊……” “哼杭棵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起氛赐,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤魂爪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后艰管,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滓侍,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年牲芋,在試婚紗的時候發(fā)現(xiàn)自己被綠了撩笆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡缸浦,死狀恐怖夕冲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情裂逐,我是刑警寧澤歹鱼,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站卜高,受9級特大地震影響弥姻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掺涛,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一蚁阳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸽照,春花似錦螺捐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诞外,卻和暖如春澜沟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背峡谊。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工茫虽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留刊苍,地道東北人。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓濒析,卻偏偏與公主長得像正什,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子号杏,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

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