CMS垃圾回收器
2 CMS簡介
CMS(Concurrent Mark and Swee 并發(fā)-標記-清除),是一款基于并發(fā)、使用標記清除算法的垃圾回收算法,只針對老年代進行垃圾回收。CMS收集器工作時,盡可能讓GC線程和用戶線程并發(fā)執(zhí)行,以達到降低STW時間的目的
通過以下命令行參數(shù),啟用CMS垃圾收集器:
-XX:+UseConcMarkSweepGC值得補充的是,下面介紹到的CMS GC是指老年代的GC,而Full GC指的是整個堆的GC事件,包括新生代、老年代、元空間等,兩者有所區(qū)分
3 新生代垃圾回收
能與CMS搭配使用的新生代垃圾收集器有Serial收集器和ParNew收集器。這2個收集器都采用標記復(fù)制算法,都會觸發(fā)STW事件,停止所有的應(yīng)用線程。不同之處在于,Serial是單線程執(zhí)行,ParNew是多線程執(zhí)行

4 老年代垃圾回收
CMS GC以獲取最小停頓時間為目的,盡可能減少STW時間,可以分為7個階段

階段 1: 初始標記(Initial Mark)
此階段的目標是標記老年代中所有存活的對象, 包括 GC Root 的直接引用, 以及由新生代中存活對象所引用的對象,觸發(fā)第一次STW事件
這個過程是支持多線程的(JDK7之前單線程,JDK8之后并行,可通過參數(shù)CMSParallelInitialMarkEnabled調(diào)整)

階段 2: 并發(fā)標記(Concurrent Mark)
此階段GC線程和應(yīng)用線程并發(fā)執(zhí)行,遍歷階段1初始標記出來的存活對象,然后繼續(xù)遞歸標記這些對象可達的對象

階段 3: 并發(fā)預(yù)清理(Concurrent Preclean)
此階段GC線程和應(yīng)用線程也是并發(fā)執(zhí)行,因為階段2是與應(yīng)用線程并發(fā)執(zhí)行,可能有些引用關(guān)系已經(jīng)發(fā)生改變。
通過卡片標記(Card Marking),提前把老年代空間邏輯劃分為相等大小的區(qū)域(Card),如果引用關(guān)系發(fā)生改變,JVM會將發(fā)生改變的區(qū)域標記位“臟區(qū)”(Dirty Card),然后在本階段,這些臟區(qū)會被找出來,刷新引用關(guān)系,清除“臟區(qū)”標記

階段 4: 并發(fā)可取消的預(yù)清理(Concurrent Abortable Preclean)
此階段也不停止應(yīng)用線程. 本階段嘗試在 STW 的 最終標記階段(Final Remark)之前盡可能地多做一些工作,以減少應(yīng)用暫停時間
在該階段不斷循環(huán)處理:標記老年代的可達對象、掃描處理Dirty Card區(qū)域中的對象,循環(huán)的終止條件有:
1 達到循環(huán)次數(shù)
2 達到循環(huán)執(zhí)行時間閾值
3 新生代內(nèi)存使用率達到閾值
階段 5: 最終標記(Final Remark)
這是GC事件中第二次(也是最后一次)STW階段,目標是完成老年代中所有存活對象的標記。在此階段執(zhí)行:
1 遍歷新生代對象,重新標記
2 根據(jù)GC Roots,重新標記
3 遍歷老年代的Dirty Card,重新標記
階段 6: 并發(fā)清除(Concurrent Sweep)
此階段與應(yīng)用程序并發(fā)執(zhí)行,不需要STW停頓,根據(jù)標記結(jié)果清除垃圾對象

階段 7: 并發(fā)重置(Concurrent Reset)
此階段與應(yīng)用程序并發(fā)執(zhí)行,重置CMS算法相關(guān)的內(nèi)部數(shù)據(jù), 為下一次GC循環(huán)做準備
5 CMS常見問題
最終標記階段停頓時間過長問題
CMS的GC停頓時間約80%都在最終標記階段(Final Remark),若該階段停頓時間過長,常見原因是新生代對老年代的無效引用,在上一階段的并發(fā)可取消預(yù)清理階段中,執(zhí)行閾值時間內(nèi)未完成循環(huán),來不及觸發(fā)Young GC,清理這些無效引用
通過添加參數(shù):-XX:+CMSScavengeBeforeRemark。在執(zhí)行最終操作之前先觸發(fā)Young GC,從而減少新生代對老年代的無效引用,降低最終標記階段的停頓,但如果在上個階段(并發(fā)可取消的預(yù)清理)已觸發(fā)Young GC,也會重復(fù)觸發(fā)Young GC
并發(fā)模式失敗(concurrent mode failure) & 晉升失敗(promotion failed)問題

并發(fā)模式失敗:當CMS在執(zhí)行回收時,新生代發(fā)生垃圾回收,同時老年代又沒有足夠的空間容納晉升的對象時,CMS 垃圾回收就會退化成單線程的Full GC。所有的應(yīng)用線程都會被暫停,老年代中所有的無效對象都被回收

晉升失敗:當新生代發(fā)生垃圾回收,老年代有足夠的空間可以容納晉升的對象,但是由于空閑空間的碎片化,導(dǎo)致晉升失敗,此時會觸發(fā)單線程且?guī)嚎s動作的Full GC
并發(fā)模式失敗和晉升失敗都會導(dǎo)致長時間的停頓,常見解決思路如下:
降低觸發(fā)CMS GC的閾值,即參數(shù)-XX:CMSInitiatingOccupancyFraction的值,讓CMS GC盡早執(zhí)行,以保證有足夠的空間
增加CMS線程數(shù),即參數(shù)-XX:ConcGCThreads,
增大老年代空間
讓對象盡量在新生代回收,避免進入老年代
內(nèi)存碎片問題
通常CMS的GC過程基于標記清除算法,不帶壓縮動作,導(dǎo)致越來越多的內(nèi)存碎片需要壓縮,常見以下場景會觸發(fā)內(nèi)存碎片壓縮:
新生代Young GC出現(xiàn)新生代晉升擔保失敗(promotion failed)
程序主動執(zhí)行System.gc()
可通過參數(shù)CMSFullGCsBeforeCompaction的值,設(shè)置多少次Full GC觸發(fā)一次壓縮,默認值為0,代表每次進入Full GC都會觸發(fā)壓縮,帶壓縮動作的算法為上面提到的單線程Serial Old算法,暫停時間(STW)時間非常長,需要盡可能減少壓縮時間
