垃圾收集器——CMS

概述

CMS 是 Concurrent Mark Sweep 的縮寫,由名字可知是一款并行的用標(biāo)記清除算法的收集器,其收集目標(biāo)是以獲取最短回收停頓時間的收集器,其是作用于老年代的垃圾收集器,要在Java虛擬機中啟用 CMS 垃圾收集器,可以使用以下JVM參數(shù):-XX:+UseConcMarkSweepGC。從Java 9 開始,CMS 被標(biāo)記為不推薦使用,并在Java 14中被標(biāo)記為已過時。Java 9及更高版本推薦使用G1垃圾收集器。因此,在選擇垃圾收集器時,最好根據(jù)具體的應(yīng)用場景和需求做出決策。

垃圾收集過程

CMS 在整個垃圾收集過程主要包含四個階段,即初始標(biāo)記、并發(fā)標(biāo)記、重新標(biāo)記、并發(fā)清除。
四個過程如圖所示:


1491695650868_.pic.jpg

由上圖可知,初始標(biāo)記和重新標(biāo)記階段是需要停止用戶線程的。

初始標(biāo)記

初始標(biāo)記即 Initial Mark 在這個階段,CMS 會暫停應(yīng)用程序的執(zhí)行,標(biāo)記出所有的根對象,并且標(biāo)記出與根對象直接關(guān)聯(lián)的對象。這個階段的停頓時間較短。

并發(fā)標(biāo)記

并發(fā)標(biāo)記即 Concurrent Mark 在這個階段,CMS 會與應(yīng)用程序并發(fā)執(zhí)行,標(biāo)記出與根對象間接關(guān)聯(lián)的對象,即進行 GCRootsTracing 的過程。這個過程需要的時間相對較長,但是與用戶線程并發(fā)執(zhí)行,因此影響較小。

重新標(biāo)記

重新標(biāo)記即 Remark 在這個階段,CMS 會暫停應(yīng)用程序的執(zhí)行,重新標(biāo)記出在并發(fā)標(biāo)記階段發(fā)生變化的對象。這個階段的停頓時間較短。

并發(fā)清除

并發(fā)清除即 Concurrent Sweep 在這個階段,CMS 會與應(yīng)用程序并發(fā)執(zhí)行,清除被標(biāo)記為垃圾的對象。這個過程需要的時間相對較長,但是與用戶線程并發(fā)執(zhí)行,因此影響較小。

優(yōu)缺點

優(yōu)點

低停頓時間: CMS 的主要優(yōu)勢在于其低停頓時間。由于大部分工作是在并發(fā)執(zhí)行的,所以應(yīng)用程序的暫停時間相對較短。
適用于響應(yīng)性應(yīng)用: 適用于需要快速響應(yīng)時間的應(yīng)用程序,因為停頓時間較短。

缺點

內(nèi)存碎片

由于 CMS 在清理垃圾時不會移動對象,可能導(dǎo)致內(nèi)存碎片的增加。這可能在長時間運行的應(yīng)用程序中導(dǎo)致性能問題。
空間碎片是由于標(biāo)記清楚算法導(dǎo)致的,所以無法避免,但是 CMS 提供了一個 -XX:+UseCMSCompactAtFullCollection 用于在 CMS 收集器頂不住要進行 FullGC 時開啟內(nèi)存碎片整理過程,由于碎片的整理過程是無法并發(fā)的,所以停頓時間會變長,該參數(shù)默認(rèn)開啟。還提供了一個參數(shù) -XX:CMSFullGCBeforeCompaction 表示執(zhí)行多少次不壓縮的 FullGC 后跟著一次帶壓縮的,默認(rèn)為 0 表示每次 FullGC 都會壓縮。

無法處理浮動垃圾

在 CMS 執(zhí)行期間,應(yīng)用程序可能會生成新的垃圾,而 CMS 無法處理這些浮動垃圾,可能需要等待下一次垃圾收集周期。
由于 CMS 收集器無法處理浮動垃圾,可能會出現(xiàn) “Concurrent Mode Failure” 失敗而導(dǎo)致另一次 FULL GC 的產(chǎn)生。因此需要預(yù)留一部分空間給并發(fā)收集過程中產(chǎn)生的浮動垃圾,可以使用 -XX:CMSInitiatingOccupancyFraction 來調(diào)節(jié)觸發(fā)的百分比,默認(rèn)該值為 92% 也就是當(dāng)老年代使用了 92% 的空間時會激活 CMS 垃圾收集器。當(dāng)出現(xiàn) “Concurrent Mode Failure” 時 CMS 會使用 SerialOld 收集器收集。

注意點

CMS 是一個老年代收集器(不是整堆收集)。因此它必須把當(dāng)前處于非收集區(qū)域的年輕代算作是 GCRoots。這跟一般的 young GC 時要把老年代的 remembered set部分算作 GCRoots 的道理一樣,只不過HotSpot沒有用卡表來記錄young -> old引用,所以就干脆掃描整個年輕代作為GCRoots。
在 CMS 初始標(biāo)記的上下文里,GCRoots 并不包括年輕代而是只有一些常規(guī)的 root。這是因為在接下來的CMS 并發(fā)標(biāo)記階段 CMS 會順著初始的根集合把年輕代里的活對象都遍歷了。所以從 CMS 初始標(biāo)記 +并發(fā)標(biāo)記 結(jié)合在一起的角度看,年輕代仍然是根集合的一部分(因為被掃描但不被收集)。
但既然 初始標(biāo)記 +并發(fā)標(biāo)記 已經(jīng)掃過了年輕代為啥還要再在重新標(biāo)記時再掃?這就是因為CMS使用的增量更新是一種 “grey mutator” 做法。CMS 重新標(biāo)記階段做的就是為了確保 grey mutator 正確性而重新掃描根集合,同時也要把卡表和 mod-union table 記錄下的在老年代里發(fā)生了變化的引用也重新掃描一遍。
前面說了 CMS 增量更新的寫屏障,只是在卡表記錄一下改變的引用的出發(fā)端對應(yīng)的卡頁。那 mod-union table是啥? 其實很簡單:卡表只有一份,既要用來支持 young GC 又要用來支持 CMS。每次 young GC 過程中都涉及重置和重新掃描卡表,這樣是滿足了 young GC 的需求,但卻破壞了 CMS 的需求——CMS 需要的信息可能被 young GC 給重置掉了。 為了避免丟失信息,就在卡表之外另外加了一個 bitmap 叫做 mod-union table。在CMS 并發(fā)標(biāo)記正在運行的過程中,每當(dāng)發(fā)生一次young GC,當(dāng)young GC要重置卡表里的某個記錄時,就會更新 mod-union table 對應(yīng)的bit。 這樣,最后到CMS 重新標(biāo)記的時候,當(dāng)時的卡表外加 mod-union table 就足以記錄在并發(fā)標(biāo)記過程中老年代發(fā)生的所有引用變化了。
PS:最后一段引用 R大的解釋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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