天天看點

CMS垃圾回收器1、CMS收集器2、CMS收集過程3、CMS垃圾收集實踐 4、CMS垃圾收集優缺點5、小結

目錄

1、CMS收集器

2、CMS收集過程

3、CMS垃圾收集實踐

 4、CMS垃圾收集優缺點

4.1、配置設定擔保機制

4.2、CMS的問題

5、小結

前言 雖然新出的G1優點明顯,但是CMS算法依然是目前項目中使用的最多的垃圾收集器,G1可能還有待時間的驗證,目前隻能是實驗階段,還未大規模普及。

1、CMS收集器

Concurrent Mark Sweep (CMS) Collector
The Concurrent Mark Sweep (CMS) collector is designed for applications that prefer shorter garbage collection pauses and 
that can afford to share processor resources with the garbage collector while the application is running.
Typically applications that have a relatively large set of long-lived data (a large old generation) and 
run on machines with two or more processors tend to benefit from the use of this collector. 
The CMS collector is enabled with the command-line option -XX:+UseConcMarkSweepGC.
The CMS collector is deprecated. Strongly consider using the Garbage-First collector instead.
           

  cms垃圾收集: https://docs.oracle.com/en/java/javase/11/gctuning/concurrent-mark-sweep-cms-collector.html#GUID-FF8150AC-73D9-4780-91DD-148E63FA1BFF     從官網的描述可以看出 ,CMS是一款更注重短暫停時間的高性能垃圾收集器,JDK7/8預設的垃圾收集器。     雖然現在項目中都是使用的CMS垃圾收集器,但因為有了G1收集器,最終還是建議放棄“deprecated”CMS使用G1垃圾收集器,之是以還有這麼多的人使用CMS,可能還是希望G1的性能保證能被歲月更多的考驗和優化,能讓其變的更加優秀。要想更好的了解G1為什麼優秀,還是要先了解曆史的垃圾收集器的更新變遷過程,是以有必要了解一下CMS垃圾收集。     HotSpot在JDK1.5推出的第一款真正意義上的并發(Concurrent)收集器,  第一次實作了讓垃圾收集線程與使用者線程(基本上)同時工作。目前很大一部分的Java應用集中在B/S系統的服務端上,這類應用尤其重視伺服器的響應速度,希望系統停頓時間最短,以給使用者帶來較好的體驗,而這也恰恰是CMS所擅長的地方。     CMS(Concurrent Mark Sweep)收集器的設計目标是: 擷取最短回收停頓時間的收集器。

2、CMS收集過程

CMS的垃圾收集一共需要經過四個階段:

  • 1. 初始标記:此階段會産生STW;這個過程隻是标記出GC ROOTS能直接關聯到的對象,耗時短,速度很快;
  • 2. 并發标記: 這個步是根據前面提到的GC ROOTS 根搜尋算法,會判定對象是“存活”還是“已死”;比如說 A -> B (A 引用 B,假設 A 是 GC Roots 關聯到的對象),那麼這個階段就是标記出 B 對象, A 對象會在初始标記中标記出來。
  • 3. 重新标記: 此階段會産生STW,主要是為了檢查校驗并發标記期間,因使用者程式繼續運作而産生變動的那一部分對象的标記記錄和新對象;
  • 4. 并發清除:該步就是清除系統中無用“以死”的對象;之後将為下次gc做準備;

由于整個過程中并發标記和并發清除,收集器線程可以與使用者線程一起工作,是以總體上來說,CMS收集器的記憶體回收過程是與使用者線程一起并發地執行的。

CMS垃圾回收器1、CMS收集器2、CMS收集過程3、CMS垃圾收集實踐 4、CMS垃圾收集優缺點5、小結

3、CMS垃圾收集實踐

CMS垃圾收集參數設定:

-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+UseConcMarkSweepGC  -Xloggc:CMS-GC.log
           
CMS垃圾回收器1、CMS收集器2、CMS收集過程3、CMS垃圾收集實踐 4、CMS垃圾收集優缺點5、小結

通過日志就可以反映出垃圾收集器的工作過程和原理:

Java HotSpot(TM) 64-Bit Server VM (25.201-b09) for bsd-amd64 JRE (1.8.0_201-b09), built on Dec 15 2018 18:35:23 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 16777216k(404032k free)

/proc/meminfo:

CommandLine flags: -XX:CMSInitiatingOccupancyFraction=30 -XX:InitialHeapSize=5242880 -XX:MaxHeapSize=5242880 -XX:MaxNewSize=1400832 -XX:MaxTenuringThreshold=6 -XX:NewSize=1400832 -XX:OldPLABSize=16 -XX:OldSize=2793472 
-XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:+UseParNewGC 
2016-07-07T17:46:27.783-0800: 0.126: [GC (Allocation Failure) 2016-07-07T17:46:27.783-0800: 0.126: [ParNew: 1088K->128K(1216K), 0.0027193 secs] 1088K->435K(6016K), 0.0028139 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

2016-07-07T17:46:28.192-0800: 0.535: [GC (CMS Initial Mark) [1 CMS-initial-mark: 1969K(4800K)] 2110K(6016K), 0.0007840 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-07-07T17:46:28.193-0800: 0.535: [CMS-concurrent-mark-start]
2016-07-07T17:46:28.194-0800: 0.537: [CMS-concurrent-mark: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-07-07T17:46:28.194-0800: 0.537: [CMS-concurrent-preclean-start]
2016-07-07T17:46:28.194-0800: 0.537: [CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-07-07T17:46:28.194-0800: 0.537: [GC (CMS Final Remark) [YG occupancy: 140 K (1216 K)]
2016-07-07T17:46:28.194-0800: 0.537: [Rescan (parallel) , 0.0002201 secs]
2016-07-07T17:46:28.194-0800: 0.537: [weak refs processing, 0.0000296 secs]
2016-07-07T17:46:28.194-0800: 0.537: [class unloading, 0.0002271 secs]
2016-07-07T17:46:28.195-0800: 0.537: [scrub symbol table, 0.0004065 secs]
2016-07-07T17:46:28.195-0800: 0.538: [scrub string table, 0.0001614 secs][1 CMS-remark: 1969K(4800K)] 2110K(6016K), 0.0011155 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-07-07T17:46:28.195-0800: 0.538: [CMS-concurrent-sweep-start]
2016-07-07T17:48:08.415-0800: 0.611: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2016-07-07T17:48:08.415-0800: 0.611: [CMS-concurrent-reset-start]
           

結果分析:youngGC裡的ParNew就不分析了,這裡主要看一下CMS的收集過程:

Phase 1: CMS-initial-mark 這是CMS中兩次stop-the-world事件中的一次。它有兩個目标:一是标記老年代中所有的GC Roots;二是标記被年輕代中活着的對象引用的對象。 Phase 2: CMS-concurrent-mark-start 這個階段會周遊整個老年代并且标記所有存活的對象,從“初始化标記”階段找到的GC Roots開始。并發标記的特點是和應用程式線程同時運作。并不是老年代的所有存活對象都會被标記,因為标記的同時應用程式會改變一些對象的引用等。 Phase 3: CMS-concurrent-preclean 這個階段又是一個并發階段,和應用線程并行運作,不會中斷他們。前一個階段在并行運作的時候,一些對象的引用已經發生了變化,當這些引用發生變化的時候,JVM會标記堆的這個區域為Dirty Card(包含被标記但是改變了的對象,被認為"dirty"),這就是 Card Marking。 Phase 4: CMS Final Remark 這個階段是CMS中第二個并且是最後一個STW的階段。該階段的任務是完成标記整個年老代的所有的存活對象。由于之前的預處理是并發的,它可能跟不上應用程式改變的速度,這個時候,STW是非常需要的來完成這個嚴酷考驗的階段。 Phase 5: CMS-concurrent-sweep-start 和應用線程同時進行,不需要STW。這個階段的目的就是移除那些不用的對象,回收他們占用的空間并且為将來使用。 Phase 6: CMS-concurrent-reset-start 這個階段并發執行,重新設定CMS算法内部的資料結構,準備下一個CMS生命周期的使用。

 4、CMS垃圾收集優缺點

目标 目标與意義:減少垃圾收集停頓時間,真正實作了垃圾收集和任務線程并行工作;
優點 并發收集 ,此階段比較耗時;由于采用并發收集可以減少停頓時間;
缺點 ( 1)CMS收集器對CPU資源非常敏感。由于和使用者線程一起工作,CPU個數少于4個時,CMS對于使用者程式的影響就可能變得很大,出現cpu資源的競争,收集期間可能出現卡頓現象; (2) CMS收集器無法處理浮動垃圾,由于和使用者線層一起工作,期間無法處理新增的浮遊垃圾; (3)CMS是基于“标記-清除”算法實作的收集器,手機結束時會有大量空間碎片産生。空間碎片過多,可能會出現老年代還有很大空間剩餘,但是無法找到足夠大的連續空間來配置設定目前對象,不得不提前出發FullGC;   (4) 産生2次STW,并發階段降低吞吐量;
使用場景 (1) 多核cpu,追求低停頓時間,犧牲吞吐量來擷取較小的暫停時間; (2) 适用于GC不頻繁的老年代,青年代使用的是ParaNew收集器;
參數使用 csm參數解析說明: -XX:+UseConcMarkSweepGC 此參數将啟動 CMS 回收器。預設新生代是 ParNew + CMS,也可以設定 Serial 為新生代收集器。該參數等價于 -Xconcgc。 -XX:ParallelGCThreads 指定線程數。預設并發線程數是:(ParallelGCThreads + 3)/ 4)。 -XX:CMSInitiatingOccupancyFraction 由于 CMS 回收器不是獨占式的,在垃圾回收的時候應用程式仍在工作,是以需要留出足夠的記憶體給應用程式,否則會觸發 FGC。而什麼時候運作 CMS GC 呢?通過該參數即可設定,該參數表示的是老年代的記憶體使用百分比。當達到這個門檻值就會執行 CMS。 預設是68。 如果老年代記憶體增長很快,建議降低門檻值,避免 FGC,如果增長慢,則可以加大門檻值,減少 CMS GC 次數。提高吞吐量。 -XX:+ UseCMSCompactAtFullCollection 由于 CMS 使用标記清理算法,記憶體碎片無法避免。該參數指定每次 CMS 後進行一次碎片整理。 -XX:CMSFullGCsBeforeCompaction 由于每次進行碎片整理将會影響性能,你可以使用該參數設定多少次 CMS 後才進行一次碎片整理,也就是記憶體壓縮。 -XX:CMSInitiatingPermOccupancyFraction 當永久區占用率達到這一百分比時,啟動 CMS 回收(前提是 -XX:+CMSClassUnloadingEnabled 激活了)。 -XX:UseCMSInitiatingOccupancyOnly 表示隻在到達門檻值的時候才進行 CMS 回收。 XX:CMSWaitDuration=2000 由于CMS GC 條件比較簡單,JVM有一個線程定時掃描Old區,時間間隔可以通過該參數指定(毫秒機關),預設是2s。

項目實戰參數設定:

MEMORY_JVM=" -Xmx4g -Xms4g -Xmn1G -Xss512K
             -XX:PermSize=128m
             -XX:MaxPermSize=256m
             -XX:SurvivorRatio=6
             -XX:InitialCodeCacheSize=64m
             -XX:ReservedCodeCacheSize=64m"

GC_JVM=" -XX:+UseConcMarkSweepGC
         -XX:CMSInitiatingOccupancyFraction=70
         -XX:CMSFullGCsBeforeCompaction=0
         -XX:+ExplicitGCInvokesConcurrent
         -XX:+UseCMSCompactAtFullCollection"
           

4.1、配置設定擔保機制

    在發生Minor GC之前,虛拟機會先檢查老年代最大可用的連續空間是否大于新生代所有對象總空間。如果這個條件成立,那麼Minor GC可以確定是安全的。 新生代使用複制收集算法,為了記憶體使用率,隻使用其中一個Survivor空間來作為輪換備份,是以當出現大量對象在Minor GC後仍然存活的情況(最極端的情況是記憶體回收之後,新生代中所有的對象都存活),就需要老年代進行配置設定擔保,把Survivor無法容納的對象直接進入老年代。老年代要進行這樣的擔保,前提是老年代本身還有容納這些對象的剩餘空間,一共有多少對象存活下來在實際完成記憶體回收之前是無法明确知道的,是以隻好取之前每一次回收晉升到老年代對象容量的平均大小值作為經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。也就是說,如果某次Minor GC存活後的對象突增,遠遠高于平均值的話,依然會導緻擔保失敗,那就隻好在失敗後重新發起一次FULL GC。

4.2、CMS的問題

cms的promotion failed & concurrent mode failure問題 問題分析: 盡管CMS使用一個叫做 配置設定擔保的機制,每次Minor GC之後要保證新生代的空間 survivor + eden > 老年帶的空閑空間,但是對象配置設定是不可預測的,總會有寫對象配置設定在老年帶是滿足不了的。 該問題是在進行MinorGC時,Survivor Space放不下,或者申請的大對象需要直接進入老年代,而此時老年代沒有足夠的空間來存儲這個對象(其中一個原因就是老年帶有足夠的空閑空間,但是比較 碎片化不連續導緻申請失敗),這時就會産生 promotion failed問題。很不幸,此時又有業務線程來申請新的記憶體空間,讓本已經不夠的堆記憶體雪上加霜,無法滿足空間需求,就會進而出現下一個問題 : concurrent mode filure,此時隻能Stop-The-Wold,垃圾回收降級為 GC-Serial Old,JVM将采用後備的串型收集器來進行垃圾回收,此時的CMS實際已經沒有什麼意義: 解 決 promotion failed :碎片整理  + 增大年輕代空間

  • -XX:UseCMSCompactAtFullCollection 每次CMS完進行碎片的整理;
  • -XX:CMSFullGCBeforeCompaction=0 每次 gc 後進行一次整理碎片操作;
  • -Xmn XX:SurvivorRatio 預設為8,通過它調大新生代或者救助空間,避免對象直接進入老年代 ;

解決 concurrent mode failure :提前觸發CMS + 增大老年代空間

  • +XX:CMSInitiatingOccupancyFraction 提前進行CMS垃圾回收,預留足夠的老年代記憶體空間,預設的值是68%, 如果項目确定有大對象的産生, 可以适當的調小,但是相應CMS的GC耗時将增加;
  • -Xms -Xmx 直接調大老年帶的空間;

5、小結

    CMS收集器是一種以擷取最短回收停頓時間為目标的收集器,具體的使用還的根據項目的場景來設定相關的參數,注意碎片問題。     水滴石穿,積少成多。學習筆記,内容簡單,用于複習,梳理鞏固。   參考資料: 《深入了解jvm虛拟機》 cms垃圾收集: https://docs.oracle.com/en/java/javase/11/gctuning/concurrent-mark-sweep-cms-collector.html#GUID-FF8150AC-73D9-4780-91DD-148E63FA1BFF