面試官:今天還是來聊聊CMS垃圾收集器唄?
候選者:嗯啊...
候選者:如果用Seria和Parallel系列的垃圾收集器:在垃圾回收的時,用戶線程都會完全停止,直至垃圾回收結束!
候選者:CMS的全稱:Concurrent Mark Sweep,翻譯過來是「並發標記清除」
候選者:用CMS對比上面的垃圾收集器(Seria和Parallel和parNew):它最大的不同點就是「並發」:在GC線程工作的時候,用戶線程「不會完全停止」,用戶線程在「部分場景下」與GC線程一起並發執行。
候選者:但是,要理解的是,無論是什么垃圾收集器,Stop The World是一定無法避免的!
候選者:CMS只是在「部分」的GC場景下可以讓GC線程與用戶線程並發執行
候選者:CMS的設計目標是為了避免「老年代 GC」出現「長時間」的卡頓(Stop The World)
面試官:那你清楚CMS的工作流程嗎?
候選者:只了解一點點,不能多了。
候選者:CMS可以簡單分為5個步驟:初始標記、並發標記、並發預清理、重新標記以及並發清除
候選者:從步驟就不難看出,CMS主要是實現了「標記清除」垃圾回收算法
面試官:嗯...是的
候選者:我就從「初始標記」來開始吧
候選者:「初始標記」會標記GCRoots「直接關聯」的對象以及「年輕代」指向「老年代」的對象
候選者:「初始標記」這個過程是會發生Stop The World的。但這個階段的速度算是很快的,因為沒有「向下追溯」(只標記一層)
候選者:在「初始標記」完了之后,就進入了「並發標記」階段啦
候選者:「並發標記」這個過程是不會停止用戶線程的(不會發生 Stop The World)。這一階段主要是從GC Roots向下「追溯」,標記所有可達的對象。
候選者:「並發標記」在GC的角度而言,是比較耗費時間的(需要追溯)
候選者:「並發標記」這個階段完成之后,就到了「並發預處理」階段啦
候選者:「並發預處理」這個階段主要想干的事情:希望能減少下一個階段「重新標記」所消耗的時間
候選者:因為下一個階段「重新標記」是需要Stop The World的
面試官:嗯...
候選者:「並發標記」這個階段由於用戶線程是沒有被掛起的,所以對象是有可能發生變化的
候選者: 可能有些對象,從新生代晉升到了老年代。可能有些對象,直接分配到了老年代(大對象)。可能老年代或者新生代的對象引用發生了變化...
面試官:那這個問題,怎么解決呢?
候選者:針對老年代的對象,其實還是可以借助類card table的存儲(將老年代對象發生變化所對應的卡頁標記為dirty)
候選者:所以「並發預處理」這個階段會掃描可能由於「並發標記」時導致老年代發生變化的對象,會再掃描一遍標記為dirty的卡頁
面試官:嗯...
候選者:對於新生代的對象,我們還是得遍歷新生代來看看在「並發標記」過程中有沒有對象引用了老年代..
候選者:不過JVM里給我們提供了很多「參數」,有可能在這個過程中會觸發一次 minor GC(觸發了minor GC 是意味着就可以更少地遍歷新生代的對象)
候選者:「並發預處理」這個階段階段結束后,就到了「重新標記」階段
候選者:「重新標記」階段會Stop The World,這個過程的停頓時間其實很大程度上取決於上面「並發預處理」階段(可以發現,這是一個追趕的過程:一邊在標記存活對象,一邊用戶線程在執行產生垃圾)
候選者:最后就是「並發清除」階段,不會Stop The World
候選者:一邊用戶線程在執行,一邊GC線程在回收不可達的對象
候選者:這個過程,還是有可能用戶線程在不斷產生垃圾,但只能留到下一次GC 進行處理了,產生的這些垃圾被叫做“浮動垃圾”
候選者:完了以后會重置 CMS 算法相關的內部數據,為下一次 GC 循環做准備
面試官:嗯,CMS的回收過程,我了解了
面試官:聽下來,其實就是把垃圾回收的過程給"細分"了,然后在某些階段可以不停止用戶線程,一邊回收垃圾,一邊處理請求,來減少每次垃圾回收時 Stop The World的時間
面試官:當然啦,中間也做了很多的優化(dirty card標記、可能中途觸發minor gc等等,在我理解下,這些應該都提供了CMS的相關參數配置)
面試官:不過,我看現在很多企業都在用G1了,那你覺得CMS有什么缺點呢?
候選者:1.空間需要預留:CMS垃圾收集器可以一邊回收垃圾,一邊處理用戶線程,那需要在這個過程中保證有充足的內存空間供用戶使用。
候選者:如果CMS運行過程中預留的空間不夠用了,會報錯(Concurrent Mode Failure),這時會啟動 Serial Old垃圾收集器進行老年代的垃圾回收,會導致停頓的時間很長。
候選者:顯然啦,空間預留多少,肯定是有參數配置的
候選者:2. 內存碎片問題:CMS本質上是實現了「標記清除算法」的收集器(從過程就可以看得出),這會意味着會產生內存碎片
候選者:由於碎片太多,又可能會導致內存空間不足所觸發full GC,CMS一般會在觸發full GC這個過程對碎片進行整理
候選者:整理涉及到「移動」/「標記」,那這個過程肯定會Stop The World的,如果內存足夠大(意味着可能裝載的對象足夠多),那這個過程卡頓也是需要一定的時間的。
面試官:嗯...
候選者:使用CMS的弊端好像就是一個死循環:
候選者:1. 內存碎片過多,導致空間利用率減低。
候選者:2. 空間本身就需要預留給用戶線程使用,現在碎片內存又加劇了空間的問題,導致有可能垃圾收集器降級為Serial Old,卡頓時間更長。
候選者:3. 要處理內存碎片的問題(整理),同樣會卡頓
候選者:不過,技術實現就是一種trade-off(權衡),不可能你把所有的事情都做得很完美
候選者:了解這個過程,是非常有趣的
面試官:那G1垃圾收集器你了解嗎
候選者:只了解一點點,不能多了
候選者:不過,留到下次吧,先讓你消化下,不然怕你頂不住了。
本文總結:
-
CMS垃圾回收器設計目的:為了避免「老年代 GC」出現「長時間」的卡頓(Stop The World)
-
CMS垃圾回收器回收過程:初始標記、並發標記、並發預處理、重新標記和並發清除。初始標記以及重新標記這兩個階段會Stop The World
-
CMS垃圾回收器的弊端:會產生內存碎片&&需要空間預留:停頓時間是不可預知的
歡迎關注我的微信公眾號【Java3y】來聊聊Java面試,對線面試官系列持續更新中!

【對線面試官-移動端】系列 一周兩篇持續更新中!
【對線面試官-電腦端】系列 一周兩篇持續更新中!
原創不易!!求三連!!