關(guān)于 CMS 垃圾回收器,你真的懂了嗎?
大家好,我是樹哥。
前段時(shí)間有個(gè)小伙伴去面試,被問到了 CMS 垃圾回收器的詳細(xì)內(nèi)容,沒答出來。實(shí)際上,CMS 垃圾回收器是回收器歷史上很重要的一個(gè)節(jié)點(diǎn),其開啟了 GC 回收器關(guān)注 GC 停頓時(shí)間的歷史。
今天,就讓樹哥帶你一起來學(xué)一波吧!
文章思維導(dǎo)圖
CMS 回收器的歷史
如果你是一個(gè)比較資深的 Java 開發(fā)者,那你或許會(huì)對(duì) CMS 垃圾回收器嗤之以鼻,然后說一句:CMS 垃圾回收器早就過時(shí)了,現(xiàn)在都流行 G1、ZGC 垃圾回收器了!學(xué)這個(gè)東西一點(diǎn)用都沒有!
確實(shí)如資深開發(fā)者所說,現(xiàn)在 CMS 垃圾回收器是比較過時(shí)的配置了。CMS 垃圾回收器于 JDK1.5 時(shí)期推出,在 JDK9 中被廢棄,在 JDK14 中被移除。 而用來替換 CMS 垃圾回收器的便是我們常說的 G1 垃圾回收器。
但 G1 垃圾回收器也是在 CMS 的基礎(chǔ)上進(jìn)行改進(jìn)的,因此簡(jiǎn)單了解下 CMS 垃圾回收器也是有必要的。
CMS 回收器簡(jiǎn)介
CMS(Concurrent Mark Sweep)垃圾回收器是第一個(gè)關(guān)注 GC 停頓時(shí)間的垃圾收集器。 在這之前的垃圾回收器,要么就是串行垃圾回收方式,要么就是關(guān)注系統(tǒng)吞吐量。
這樣的垃圾回收器對(duì)于強(qiáng)交互的程序很不友好,而 CMS 垃圾回收器的出現(xiàn),則打破了這個(gè)尷尬的局面。因此,CMS 垃圾回收器誕生之后就受到了大家的歡迎,導(dǎo)致現(xiàn)在還有非常多的應(yīng)用還在繼續(xù)使用它。
CMS 垃圾回收器之所以能夠?qū)崿F(xiàn)對(duì) GC 停頓時(shí)間的控制,其本質(zhì)來源于對(duì)「根可達(dá)算法」的改進(jìn),即三色標(biāo)記算法。在 CMS 垃圾回收器出現(xiàn)之前,無論是 Serious 垃圾回收器,還是 ParNew 垃圾回收器,亦或是 Parallel Scavenge 垃圾回收器,他們?cè)谶M(jìn)行垃圾回收的時(shí)候都需要 Stop the World,即無法實(shí)現(xiàn)垃圾回收線程與用戶線程并發(fā)執(zhí)行。
而 CMS 垃圾回收器通過三色標(biāo)記算法,實(shí)現(xiàn)了垃圾回收線程與用戶線程并發(fā)執(zhí)行,從而極大地降低了系統(tǒng)響應(yīng)時(shí)間,提高了強(qiáng)交互應(yīng)用程序的體驗(yàn)。
對(duì)于 CMS 垃圾回收器來說,其實(shí)通過「標(biāo)記 - 清除」算法實(shí)現(xiàn)的,它的運(yùn)行過程分為 4 個(gè)步驟,包括:
- 初始標(biāo)記
- 并發(fā)標(biāo)記
- 重新標(biāo)記
- 并發(fā)清除
初始標(biāo)記,指的是尋找所有被 GCRoots 引用的對(duì)象,該階段需要「Stop the World」。 這個(gè)步驟僅僅只是標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對(duì)象,并不需要做整個(gè)引用的掃描,因此速度很快。
并發(fā)標(biāo)記,指的是對(duì)「初始標(biāo)記階段」標(biāo)記的對(duì)象進(jìn)行整個(gè)引用鏈的掃描,該階段不需要「Stop the World」。 對(duì)整個(gè)引用鏈做掃描需要花費(fèi)非常多的時(shí)間,因此通過垃圾回收線程與用戶線程并發(fā)執(zhí)行,可以降低垃圾回收的時(shí)間,從而降低系統(tǒng)響應(yīng)時(shí)間。
這也是 CMS 垃圾回收器能極大降低 GC 停頓時(shí)間的核心原因,但這也帶來了一些問題,即:并發(fā)標(biāo)記的時(shí)候,引用可能發(fā)生變化,因此可能發(fā)生漏標(biāo)(本應(yīng)該回收的垃圾沒有被回收)和多標(biāo)(本不應(yīng)該回收的垃圾被回收)了。
重新標(biāo)記,指的是對(duì)「并發(fā)標(biāo)記」階段出現(xiàn)的問題進(jìn)行校正,該階段需要「Stop the World」。 正如并發(fā)標(biāo)記階段說到的,由于垃圾回收算法和用戶線程并發(fā)執(zhí)行,雖然能降低響應(yīng)時(shí)間,但是會(huì)發(fā)生漏標(biāo)和多標(biāo)的問題。所以對(duì)于 CMS 回收器來說,它需要這個(gè)階段來做一些校驗(yàn),解決并發(fā)標(biāo)記階段發(fā)生的問題。
并發(fā)清除,指的是將標(biāo)記為垃圾的對(duì)象進(jìn)行清除,該階段不需要「Stop the World」。 在這個(gè)階段,垃圾回收線程與用戶線程可以并發(fā)執(zhí)行,因此并不影響用戶的響應(yīng)時(shí)間。
引用自《深入理解 Java 虛擬機(jī)》
從上面的描述步驟中我們可以看出:CMS 之所以能極大地降低 GC 停頓時(shí)間,本質(zhì)上是將原本冗長(zhǎng)的引用鏈掃描進(jìn)行切分。通過 GC 線程與用戶線程并發(fā)執(zhí)行,加上重新標(biāo)記校正的方式,減少了垃圾回收的時(shí)間。
CMS 回收器優(yōu)缺點(diǎn)
從上面的描述我們可以知道,CMS 回收器的優(yōu)點(diǎn)是:并發(fā)收集垃圾、低停頓。但其也有下面幾個(gè)明顯的缺點(diǎn):
對(duì) CPU 資源消耗較大。 CMS 回收器在并發(fā)標(biāo)記和并發(fā)清理階段,是需要啟用多個(gè)線程進(jìn)行處理的,這就意味著它需要占用一部分線程資源,即 CPU 資源。
默認(rèn)情況下 CMS 啟用的垃圾回收線程數(shù)是(CPU數(shù)量 + 3)/4,當(dāng) CPU 數(shù)量越大時(shí),啟用的垃圾回收線程數(shù)占比就越小。
但如果 CPU 數(shù)量越小,例如只有 2 個(gè) CPU 時(shí),垃圾回收線程占用就達(dá)到了 50%,也就是說需要拿 50% 的 CPU 時(shí)間來進(jìn)行垃圾回收。這就會(huì)極大地降低系統(tǒng)的吞吐量,這是讓人無法接受的情況。
無法處理浮動(dòng)垃圾。 由于 CMS 并發(fā)標(biāo)記階段會(huì)發(fā)生漏標(biāo)的情況,因此會(huì)有一些本該回收的垃圾對(duì)象無法被回收。
此外,在 CMS 進(jìn)行并發(fā)清理的時(shí)候,用戶線程同時(shí)在運(yùn)行,也會(huì)產(chǎn)生一些浮動(dòng)垃圾。因此對(duì)于 CMS 回收器來說,其需要留出一些空間給這些浮動(dòng)垃圾存儲(chǔ)。
在 JDK1.5 的默認(rèn)設(shè)置中,當(dāng)老年代空間已用空間大于 68% 之后,CMS 垃圾回收器便會(huì)開始進(jìn)行垃圾清理。這個(gè)數(shù)值相對(duì)比較保守一些,我們可以通過 -XX:CMSInitiatingOccupancyFraction 參數(shù)自行調(diào)節(jié)。
在 JDK1.6 種,該閾值被提升至 92%。如果在 CMS 運(yùn)行期間發(fā)現(xiàn)預(yù)留的內(nèi)存無法滿足程序需要,就會(huì)提示「Concurrent Mode Failure」錯(cuò)誤。
此時(shí)虛擬機(jī)采用后備方案:臨時(shí)啟用 Serial Old 回收器來重新進(jìn)行老年代的垃圾回收,這時(shí)候 Stop the World 的時(shí)間可能就會(huì)很長(zhǎng)了。
產(chǎn)生空間碎片。 由于 CMS 是基于「標(biāo)記 - 清除」算法實(shí)現(xiàn)的回收器,因此其會(huì)產(chǎn)生很多空間碎片,這會(huì)導(dǎo)致給大對(duì)象分配的時(shí)候很麻煩,會(huì)提前觸發(fā) Full GC。為了解決這個(gè)問題,CMS 回收器提供了 -XX:+UseCMSCompactAtFullCollection 參數(shù)來解決這個(gè)問題,意思是在空間不夠的時(shí)候進(jìn)行空間整理,這個(gè)參數(shù)默認(rèn)是打開的。
該參數(shù)通常和 -XX:CMSFullGCsBeforeCompaction 一起使用,后者用于設(shè)置執(zhí)行多少次不壓縮的 Full GC 之后,跟著來一次帶壓縮的 Full GC(默認(rèn)值是 0,表示每次進(jìn)入 Full GC 時(shí)都進(jìn)行碎片整理)。
總結(jié)
CMS 回收器,誕生于 JDK1.5,失落于 JDK9,卒于 JDK14。它的誕生,開啟了垃圾回收器專注于優(yōu)化 GC 停頓時(shí)間的歷史,隨后的 G1、ZGC 都在 CMS 的基礎(chǔ)之上改進(jìn)、優(yōu)化而來。
而 CMS 回收器之所以能實(shí)現(xiàn)對(duì) GC 停頓時(shí)間的強(qiáng)力控制,全都?xì)w功于對(duì)于「根可達(dá)算法」的優(yōu)化。其將串行的引用鏈掃描,拆分成了「初始標(biāo)記」和「并發(fā)標(biāo)記」兩個(gè)階段,從而極大地降低了 GC 停頓時(shí)間,最后再通過「重新標(biāo)記」解決了并發(fā)執(zhí)行產(chǎn)生的問題。
參考資料
CMS 低延遲垃圾收集器詳解 - 掘金
深入理解 JAVA 垃圾收集器 CMS,G1 工作流程原理 - 掘金
深入理解 Java 虛擬機(jī):JVM 高級(jí)特性與最佳實(shí)踐(第 2 版)- 周志明 - 微信讀書?