1. 程式人生 > >JVM垃圾收集演算法-CMS

JVM垃圾收集演算法-CMS

開發十年,就只剩下這套Java開發體系了 >>>   hot3.png

一、概述

    CMS為老年代的併發收集器,即在對老年代進行gc時,與使用者執行緒併發執行;基於標記-清除演算法實現,對於大於3G小於8G的堆較合適。gc過程分為:初始標記(其中標記為標記活動的物件,沒有被標記的則表示可回收的物件),併發標記,重新標記,併發清除和併發重置五個階段,其中初始標記,重新標記會出現STW,如下gc日誌為顯示這兩種標記導致應用停頓的時間:

1f076ffb0f6251e917fbcf8af19ea4ec05c.jpg        可通過:cat gc.log | grep -A 1 "CMS Final Remark",進一步檢視本次CMS造成應用實際停頓的時間。如下:

882947fcc59803c3d07c2cb335420c4cfdc.jpg

 二、CMS對老年代進行垃圾收集的詳細過程

        1. 初始標記initMark:採用可達性分析,在老年代堆中標記GC ROOTS可以直接到達的物件,所以很快;

        2. 併發標記:以初始標記中可達的物件為起點,標記所有可達物件,此過程與使用者執行緒併發執行。之所以將標記分為初始標記和併發標記,而不是在一次標記裡面完成,是因為通過在初始標記只標記從GC ROOTS直接可達的物件,而不繼續往下標記,這樣可以減少STW的時間,在併發標記時,再繼續往下標記,此時與使用者執行緒併發執行,不會造成停頓;

        3. 重新標記Remark

        (1)在併發標記階段,使用者執行緒在併發執行,所以此時可能出現某些物件從初始標記階段時的不可達,在併發標記時,使用者執行緒更新而變成可達,所以需要進行重新標記來避免這些物件被清理。這個階段由於存在”跨代引用“,即新生代引用老年代,所以該階段是從新生代開始的,所以涉及整個堆的掃描來確定哪些物件還存活。所以如果此時新生代存在較多存活物件,該階段就會存在較長耗時。

       (2)由於重新標記會掃描整個堆中的物件,包括新生代和老年代,為了縮短該階段造成的STW,CMS會在該Remark之前,進行一次可中斷的併發預清理(CMS-concurrent-abortable-preclean):

        併發預清理:與使用者執行緒併發執行,功能是將新生代中在併發標記過程中晉升到老年代的物件清除,從而減少Remark所需掃描物件的數量。具體發生與否要看Eden的大小是否大於2M,這是預設值,可以修改。增加這個階段的好處如果在這個階段發生了MG,則此時新生代的大小就會變小,這樣重新標記所需掃描物件就會減少,減少造成應用STW的時間。而CMS-concurrent-abortable-preclean也不是無限進行的,通過引數CMSMaxAbortablePrecleanTime,預設為5s,在CMS-concurrent-abortable-preclean執行超過5s時,則直接進入重新標記階段。

      (3)除了以上所說優化外,CMS還可以通過設定CMSScavengeBeforeRemark引數,強制Remark前執行一次MG,從而回收新生代中物件,減少新生代中需要掃描的物件。

        4. 併發清除:併發清理年老代中除以上標記還存活物件之外的其他物件,此過程不會造成應用STW,同時也不會對記憶體進行壓縮整理,會出現記憶體碎片。清除後,CMS不是簡單地用一根指標執行未分配的空間,而是彙總到一個列表當中,當需要分配記憶體時,從該列表中尋找能放下該物件的記憶體空間。

        5. 併發重置:與CMS相關的資料結構並重新初始化,準備下一次CMS收集。

        整個標記清理過程如圖:

b687c455c80137b407993e385ec7992363c.jpg

       各階段執行緒分佈於執行情況:

5eaac09162c4c60c004c7055981d34ffd0e.jpg

三、拓展思考

    1. 何時壓縮老年代空間:CMS使用的是標記清除演算法,不會對老年代堆進行壓縮,而CMS是屬於Major GC,在Major GC後仍舊無法容納物件時,則發生Full GC, Full GC會對老年代堆進行壓縮。

    2. 如果判斷CMS演算法效果:主要檢視gc日誌的初始標記initial mark和重新標記final remark的real耗時,這兩個階段會造成STW。同時整體效果需要檢視stopped的時間,根據這個判斷是否達到應用需求,如上面截圖。

    3. 新生代垃圾收集器:CMS只能與ParNew新生代垃圾收集器一起使用,開啟為:-XX:+UseParNewGC,也可以不加該引數,CMS預設使用ParNew作為新生代垃圾收集器。

    4.什麼情況下CMS比較適合:

    (1)響應時間優先,能接受犧牲一定的吞吐量,如果需要高響應時間和高吞吐量,推薦使用G1。後續文章再繼續介紹G1。G1也是JDK9預設的垃圾收集器;

    (2)硬體配置較高,即CPU和記憶體資源充足。CMS由於需要與使用者執行緒併發執行,所以可能會競爭CPU資源。同時CMS併發標記階段,使用者執行緒同時執行時會新建物件,故記憶體佔用會比較高;

    (3)堆大小在3G到8G之間,同時存活時間較長的物件比較多。

 

推薦閱讀:

https://www.oracle.com/technetwork/tutorials/tutorials-1876574.html

https://tech.meituan.com/jvm_optimize.html

 

223916_bL9y_2663968.jpg