關(guān)于 CMS 垃圾回收器,你真的懂了嗎?
大家好,我是樹哥。
前段時間有個小伙伴去面試,被問到了 CMS 垃圾回收器的詳細內(nèi)容,沒答出來。實際上,CMS 垃圾回收器是回收器歷史上很重要的一個節(jié)點,其開啟了 GC 回收器關(guān)注 GC 停頓時間的歷史。
今天,就讓樹哥帶你一起來學(xué)一波吧!

文章思維導(dǎo)圖
CMS 回收器的歷史
如果你是一個比較資深的 Java 開發(fā)者,那你或許會對 CMS 垃圾回收器嗤之以鼻,然后說一句:CMS 垃圾回收器早就過時了,現(xiàn)在都流行 G1、ZGC 垃圾回收器了!學(xué)這個東西一點用都沒有!
確實如資深開發(fā)者所說,現(xiàn)在 CMS 垃圾回收器是比較過時的配置了。CMS 垃圾回收器于 JDK1.5 時期推出,在 JDK9 中被廢棄,在 JDK14 中被移除。 而用來替換 CMS 垃圾回收器的便是我們常說的 G1 垃圾回收器。
但 G1 垃圾回收器也是在 CMS 的基礎(chǔ)上進行改進的,因此簡單了解下 CMS 垃圾回收器也是有必要的。
CMS 回收器簡介
CMS(Concurrent Mark Sweep)垃圾回收器是第一個關(guān)注 GC 停頓時間的垃圾收集器。 在這之前的垃圾回收器,要么就是串行垃圾回收方式,要么就是關(guān)注系統(tǒng)吞吐量。
這樣的垃圾回收器對于強交互的程序很不友好,而 CMS 垃圾回收器的出現(xiàn),則打破了這個尷尬的局面。因此,CMS 垃圾回收器誕生之后就受到了大家的歡迎,導(dǎo)致現(xiàn)在還有非常多的應(yīng)用還在繼續(xù)使用它。
CMS 垃圾回收器之所以能夠?qū)崿F(xiàn)對 GC 停頓時間的控制,其本質(zhì)來源于對「根可達算法」的改進,即三色標(biāo)記算法。在 CMS 垃圾回收器出現(xiàn)之前,無論是 Serious 垃圾回收器,還是 ParNew 垃圾回收器,亦或是 Parallel Scavenge 垃圾回收器,他們在進行垃圾回收的時候都需要 Stop the World,即無法實現(xiàn)垃圾回收線程與用戶線程并發(fā)執(zhí)行。
而 CMS 垃圾回收器通過三色標(biāo)記算法,實現(xiàn)了垃圾回收線程與用戶線程并發(fā)執(zhí)行,從而極大地降低了系統(tǒng)響應(yīng)時間,提高了強交互應(yīng)用程序的體驗。
對于 CMS 垃圾回收器來說,其實通過「標(biāo)記 - 清除」算法實現(xiàn)的,它的運行過程分為 4 個步驟,包括:
- 初始標(biāo)記
 - 并發(fā)標(biāo)記
 - 重新標(biāo)記
 - 并發(fā)清除
 
初始標(biāo)記,指的是尋找所有被 GCRoots 引用的對象,該階段需要「Stop the World」。 這個步驟僅僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象,并不需要做整個引用的掃描,因此速度很快。
并發(fā)標(biāo)記,指的是對「初始標(biāo)記階段」標(biāo)記的對象進行整個引用鏈的掃描,該階段不需要「Stop the World」。 對整個引用鏈做掃描需要花費非常多的時間,因此通過垃圾回收線程與用戶線程并發(fā)執(zhí)行,可以降低垃圾回收的時間,從而降低系統(tǒng)響應(yīng)時間。
這也是 CMS 垃圾回收器能極大降低 GC 停頓時間的核心原因,但這也帶來了一些問題,即:并發(fā)標(biāo)記的時候,引用可能發(fā)生變化,因此可能發(fā)生漏標(biāo)(本應(yīng)該回收的垃圾沒有被回收)和多標(biāo)(本不應(yīng)該回收的垃圾被回收)了。
重新標(biāo)記,指的是對「并發(fā)標(biāo)記」階段出現(xiàn)的問題進行校正,該階段需要「Stop the World」。 正如并發(fā)標(biāo)記階段說到的,由于垃圾回收算法和用戶線程并發(fā)執(zhí)行,雖然能降低響應(yīng)時間,但是會發(fā)生漏標(biāo)和多標(biāo)的問題。所以對于 CMS 回收器來說,它需要這個階段來做一些校驗,解決并發(fā)標(biāo)記階段發(fā)生的問題。
并發(fā)清除,指的是將標(biāo)記為垃圾的對象進行清除,該階段不需要「Stop the World」。 在這個階段,垃圾回收線程與用戶線程可以并發(fā)執(zhí)行,因此并不影響用戶的響應(yīng)時間。

引用自《深入理解 Java 虛擬機》
從上面的描述步驟中我們可以看出:CMS 之所以能極大地降低 GC 停頓時間,本質(zhì)上是將原本冗長的引用鏈掃描進行切分。通過 GC 線程與用戶線程并發(fā)執(zhí)行,加上重新標(biāo)記校正的方式,減少了垃圾回收的時間。
CMS 回收器優(yōu)缺點
從上面的描述我們可以知道,CMS 回收器的優(yōu)點是:并發(fā)收集垃圾、低停頓。但其也有下面幾個明顯的缺點:
對 CPU 資源消耗較大。 CMS 回收器在并發(fā)標(biāo)記和并發(fā)清理階段,是需要啟用多個線程進行處理的,這就意味著它需要占用一部分線程資源,即 CPU 資源。
默認情況下 CMS 啟用的垃圾回收線程數(shù)是(CPU數(shù)量 + 3)/4,當(dāng) CPU 數(shù)量越大時,啟用的垃圾回收線程數(shù)占比就越小。
但如果 CPU 數(shù)量越小,例如只有 2 個 CPU 時,垃圾回收線程占用就達到了 50%,也就是說需要拿 50% 的 CPU 時間來進行垃圾回收。這就會極大地降低系統(tǒng)的吞吐量,這是讓人無法接受的情況。
無法處理浮動垃圾。 由于 CMS 并發(fā)標(biāo)記階段會發(fā)生漏標(biāo)的情況,因此會有一些本該回收的垃圾對象無法被回收。
此外,在 CMS 進行并發(fā)清理的時候,用戶線程同時在運行,也會產(chǎn)生一些浮動垃圾。因此對于 CMS 回收器來說,其需要留出一些空間給這些浮動垃圾存儲。
在 JDK1.5 的默認設(shè)置中,當(dāng)老年代空間已用空間大于 68% 之后,CMS 垃圾回收器便會開始進行垃圾清理。這個數(shù)值相對比較保守一些,我們可以通過 -XX:CMSInitiatingOccupancyFraction 參數(shù)自行調(diào)節(jié)。
在 JDK1.6 種,該閾值被提升至 92%。如果在 CMS 運行期間發(fā)現(xiàn)預(yù)留的內(nèi)存無法滿足程序需要,就會提示「Concurrent Mode Failure」錯誤。
此時虛擬機采用后備方案:臨時啟用 Serial Old 回收器來重新進行老年代的垃圾回收,這時候 Stop the World 的時間可能就會很長了。
產(chǎn)生空間碎片。 由于 CMS 是基于「標(biāo)記 - 清除」算法實現(xiàn)的回收器,因此其會產(chǎn)生很多空間碎片,這會導(dǎo)致給大對象分配的時候很麻煩,會提前觸發(fā) Full GC。為了解決這個問題,CMS 回收器提供了 -XX:+UseCMSCompactAtFullCollection 參數(shù)來解決這個問題,意思是在空間不夠的時候進行空間整理,這個參數(shù)默認是打開的。
該參數(shù)通常和 -XX:CMSFullGCsBeforeCompaction 一起使用,后者用于設(shè)置執(zhí)行多少次不壓縮的 Full GC 之后,跟著來一次帶壓縮的 Full GC(默認值是 0,表示每次進入 Full GC 時都進行碎片整理)。
總結(jié)
CMS 回收器,誕生于 JDK1.5,失落于 JDK9,卒于 JDK14。它的誕生,開啟了垃圾回收器專注于優(yōu)化 GC 停頓時間的歷史,隨后的 G1、ZGC 都在 CMS 的基礎(chǔ)之上改進、優(yōu)化而來。
而 CMS 回收器之所以能實現(xiàn)對 GC 停頓時間的強力控制,全都歸功于對于「根可達算法」的優(yōu)化。其將串行的引用鏈掃描,拆分成了「初始標(biāo)記」和「并發(fā)標(biāo)記」兩個階段,從而極大地降低了 GC 停頓時間,最后再通過「重新標(biāo)記」解決了并發(fā)執(zhí)行產(chǎn)生的問題。
參考資料
CMS 低延遲垃圾收集器詳解 - 掘金
深入理解 JAVA 垃圾收集器 CMS,G1 工作流程原理 - 掘金
深入理解 Java 虛擬機:JVM 高級特性與最佳實踐(第 2 版)- 周志明 - 微信讀書?















 
 
 











 
 
 
 