穩了!我準備了1個晚上的CMS垃圾收集器

Java3y發表於2021-11-09

面試官今天還是來聊聊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面試,對線面試官系列持續更新中!

【對線面試官-移動端】系列 一週兩篇持續更新中!
【對線面試官-電腦端】系列 一週兩篇持續更新中!

原創不易!!求三連!!

相關文章