文章目錄
Pre
JVM-04垃圾收集Garbage Collection(上)【垃圾對象的判定】
JVM-05垃圾收集Garbage Collection(中)【垃圾收集算法】
JVM-06垃圾收集Garbage Collection(下)【垃圾收集器】
概述
Concurrent Mark Sweep 併發標記清除 。
CMS 收集器是一種以獲取最短回收停頓時間爲目標的收集器。
它非常符合在注重用戶體驗的應用上使用,它是HotSpot虛擬機第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工作 。
從名字( Mark Sweep )上也可以看出 CMS收集器是一種 “標記-清除”算法實現的垃圾收集器。
階段
初始標記 (STW)
初始標記階段,會Stop the word , 這個階段CMS僅標記被GC Root直接引用的對象,所以速度很快。
爲什麼這個階段要STW呢?
試想一下,如果初始標記階段 GC線程和用戶線程同時運行,GC的過程中,用戶線程還是會有新的垃圾對象產生 ,那啥時候能標記完,徒增複雜。 同時CMS設計的理念就是用戶感知至上,所以雖然STW,但也儘量縮短STW的時間,所以選擇了僅標記被GC Root直接引用的對象,而無需遍歷整個堆 。
併發標記 (用戶線程和GC線程並行工作)
經過了上一步的初始標記, 已經將GC Root 直接引用的對象標記完成。
CMS 老年代的Garbage Collector , 回收的是整個堆的垃圾對象,效率隨着堆的大小成反比 ( 如果堆比較大,比如10G,CMS也是有點喫力的,所以纔有了G1)
爲了避免GC時間過長,這個階段CMS 讓用戶線程和 GC線程同時工作,儘量減少用戶線程的停頓。 GC線程這個時候就要從GC Roots的直接關聯對象開始遍歷整個對象圖,這個耗時最長。 所以CMS重點優化了這塊 。 I
用戶線程和GC線程並行工作,多少都會存在一些問題 。因爲用戶程序繼續運行,可能會有導致已經標記過的對象狀態發生改變。
試想一下 GC工作的時候,用戶線程也在工作
- 如果GC完成後,當時遍歷的用戶線程引用的對象由不是垃圾對象,變成了垃圾對象 ,那是不是就 漏標了?
- 如果GC線程運行中,當時標記的對象是垃圾對象,但是用戶線程運行的過程中又把這個對象重新引用了,那是不是 錯標了 ?
怎麼辦呢? CMS設計了 下個階段 重新標記 來修復這個階段因對象狀態變更導致的標記錯誤。
重新標記 (STW)
這個階段主要是修正併發標記期間因爲用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄。 所以這個時候要STW,不然還是會出現階段二的情況。
這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比並發標記階段時間短
如何重新標記呢? 這裏只要採用 三色標記中的增量更新算法。
併發清理(用戶線程和GC線程並行工作)
又是並行執行
如果這個清理階段又有新的對象進來(肯定沒有被標記,因爲上一步已經標記過了),這個時候怎麼辦? 也刪掉? 還不是垃圾對象啊 刪掉那肯定不行。該怎麼辦呢?
CMS是這樣處理的: 對於新增對象會被標記爲黑色不做任何處理
這樣的對象被稱爲 【浮動垃圾】, 標記黑色,本次不處理,等下次GC。
併發重置
經過這麼一輪的標記,最後的階段 肯定是把 標記清除掉,等待下一輪 ~
concurrent mode failure 是怎麼回事兒
本身full GC 就是因爲老年代沒有空間了, 在 併發標記 和 併發清除階段 是 用戶線程和GC線程 並行執行, 如果這個時候 用戶線程又產生了一些大對象或者符合條件的對象晉升到了老年代, 這個時候 老年代沒有空間存放這些對象了,GC一邊回收,系統一邊運行,也許沒回收完就再次觸發full gc , 就出現了 “concurrent mode failure” .
出現這個情況,CMS是怎麼處理呢? 直接OOM?
顯然不是,CMS會Stop the word , 然後使用Serial Old 單線程 來進行垃圾回收。 儘量避免這種情況,效率非常低。
如何避免呢? 可以合理設置CMS的參數 (-XX:CMSInitiatingOccupancyFraction
) 默認92% ,可以將這個值設置爲80%(根據業務考量該值,你的系統大對象多的話 ,當然了,你設置了 80% 就意味着你老年代將會有20%的空間不可用,這部分空間僅能用來存放GC過程中新的對象,需要合理評估),儘量避免併發失敗的情況的發生。
CMS核心參數
-XX:+UseConcMarkSweepGC
:啓用cms-XX:ConcGCThreads
:併發的GC線程數-XX:+UseCMSCompactAtFullCollection
:FullGC之後做壓縮整理,其目的是爲了減少內存碎片-XX:CMSFullGCsBeforeCompaction
:多少次FullGC之後壓縮一次,默認是0,代表每次FullGC後都會壓縮一次-XX:CMSInitiatingOccupancyFraction
: 當老年代使用達到該比例時會觸發FullGC(默認是92%)-XX:+UseCMSInitiatingOccupancyOnly
:只使用設定的回收閾值(-XX:CMSInitiatingOccupancyFraction
設定的值),如果不指定,JVM僅在第一次使用設定值,後續則會自動調整-XX:+CMSScavengeBeforeRemark
:在CMS GC前啓動一次minor gc,目的在於減少老年代對年輕代的引用,降低CMS GC的標記階段時的開銷,一般CMS的GC耗時 80%都在標記階段-XX:+CMSParallellnitialMarkEnabled
:表示在初始標記的時候多線程執行,縮短STW的時間-XX:+CMSParallelRemarkEnabled
:在重新標記的時候多線程執行,縮短STW的時間
CMS的優缺點
優點
- 併發收集
- 低停頓 ,用戶體驗至上
缺點:
- 用戶線程和GC線程有可能爭搶CPU資源
- 無法處理浮動垃圾 , 在併發標記和併發清理階段又產生垃圾,這種浮動垃圾只能等到下一次gc再清理了
- 回收算法-“標記-清除”算法會導致收集結束時會有大量空間碎片產生, 可以通過參數
-XX:+UseCMSCompactAtFullCollection
讓jvm在執行完標記清除後再做整理,從而實現“標記-整理”的效果,減少內存碎片。 還有個參數-XX:CMSFullGCsBeforeCompaction
代表多少次Full GC以後整理一下內存碎片,默認爲0 即每次Full GC之後都會整理內存碎片。 - 執行過程中的不確定性,會存在上一次垃圾回收還沒執行完,然後垃圾回收又被觸發的情況 。 比如在併發標記和併發清理階段會出現concurrent mode failure,這個時候會STW,會切換到Serial Old ,效率非常低。
ParNew + CMS設置Demo
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=3
沒有標準答案,需要依據自己的業務特點,進行合理的參數調優。
GC算法
三色標記
在併發標記的過程中,應用線程也在運行,所以GC線程在運行期間應用線程使用的對象之間的引用可能發生變化,會存在多標和漏標的現象。
JVM把Gcroots可達性分析遍歷對象過程中遇到的對象, 按照“是否訪問過”這個條件標記成以下三種顏色 : 黑 灰 白
-
黑色: 表示對象已經被垃圾收集器訪問過, 且這個對象的所有引用都已經掃描過。 黑色的對象代表已經掃描過, 它是安全存活的, 如果有其他對象引用指向了黑色對象, 無須重新掃描一遍。 黑色對象不可能直接(不經過灰色對象) 指向某個白色對象。
-
灰色: 表示對象已經被垃圾收集器訪問過, 但這個對象上至少存在一個引用還沒有被掃描過。
-
白色: 表示對象尚未被垃圾收集器訪問過。 顯然在可達性分析剛剛開始的階段, 所有的對象都是白色的, 若在分析結束的階段, 仍然是白色的對象, 即代表不可達。