首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

CMS 垃圾收集器全流程詳解與 GC 日誌深度分析

     編輯:Java 綜合教學

CMS 收集器核心原理與日誌實戰解讀

作為 JDK 早期主流的低延遲垃圾收集器,CMS(Concurrent Mark Sweep)以「大部分階段並發執行」著稱。本文結合 JDK 8 實際 GC 日誌,從收集器組合、核心設計目標、七大並發/STW 階段流程、日誌字段逐行拆解、對象晉升量計算等維度,帶你徹底看懂 CMS 全生命周期。

一、收集器組合與設計目標

  • 年輕代:ParNew(Stop-The-World、Mark-Copy、並行)
  • 年老代:CMS(Mostly Concurrent Mark-Sweep)
  • 核心目標:避免年老代收集產生長時間停頓
  • 關鍵機制:
    1. 不壓縮整理年老代,改用 Free Lists 管理回收空間;
    2. 將標記-清除拆分為多個階段,大部分階段與用戶線程並發執行(默認 GC 線程數 = CPU 核數 / 4)。
  • 適用場景:多核 CPU、追求低延遲、能容忍一定吞吐量下降的服務端應用。

二、Minor GC 日誌逐字段拆解

2016-08-23T02:23:07.219-0200: 64.322: [GC (Allocation Failure) 
64.322: [ParNew: 613404K->68068K(613440K), 0.1020465 secs] 
10885349K->10880154K(12514816K), 0.1021309 secs] 
[Times: user=0.78 sys=0.01, real=0.11 secs]
字段含義
2016-08-23T02:23:07.219-0200GC 發生的絕對時間(含時區)
64.322相對 JVM 啟動的秒數
GC標識 Minor GC(非 Full GC)
Allocation Failure觸發原因:年輕代空間不足
ParNew年輕代收集器名稱
613404K->68068K(613440K)年輕代收集前/後佔用、總容量
0.1020465 secs年輕代收集耗時(不含最終清理)
10885349K->10880154K(12514816K)整堆收集前/後佔用、總容量
0.1021309 secs整個 GC 事件總耗時
Timesuser=GC 線程總 CPU 時間;sys=系統調用/等待;real=應用實際停頓時間

對象晉升量精確計算

  • GC 前:老年代 = 10885349K - 613404K = 10271945K
  • GC 後:老年代 = 10880154K - 68068K = 10812086K
  • 晉升量 = 10812086K - 10271945K = 608209K(約 594 MB)

這說明本次 Minor GC 有大量對象未在年輕代存活,直接晉升至老年代,可能預示著年輕代過小或對象存活率異常。

三、CMS Full GC 七大階段深度解析

完整日誌片段:

[GC (CMS Initial Mark) [1 CMS-initial-mark: 10812086K(11901376K)] ...]
[CMS-concurrent-mark-start] ... [CMS-concurrent-mark: 0.035/0.035 secs]
[CMS-concurrent-preclean-start] ... [CMS-concurrent-preclean: 0.016/0.016 secs]
[CMS-concurrent-abortable-preclean-start] ... [CMS-concurrent-abortable-preclean: 0.167/1.074 secs]
[GC (CMS Final Remark) [YG occupancy: 387920 K (613440 K)] ...]
[CMS-concurrent-sweep-start] ... [CMS-concurrent-sweep: 0.027/0.027 secs]
[CMS-concurrent-reset-start] ... [CMS-concurrent-reset: 0.012/0.012 secs]

Phase 1:Initial Mark(初始標記,STW)

  • 目標:標記所有 GC Roots 及被年輕代存活對象直接引用的老年代對象。
  • 日誌關鍵:CMS-initial-mark: 10812086K(11901376K) 老年代佔用/容量;10887844K(12514816K) 整堆佔用/容量。
  • 耗時極短(示例 0.0002s),因只掃描 Roots 而非整個老年代。

Phase 2:Concurrent Mark(並發標記)

  • 從 Roots 出發遍歷整個老年代,標記所有可達對象;與用戶線程並發運行。
  • 因並發修改引用,部分對象可能漏標或誤標,需後續階段修正。

Phase 3:Concurrent Preclean(並發預清理)

  • 處理並發標記階段產生的 Dirty Cards(被修改引用的卡頁)。
  • 將從 Dirty Cards 可達的對象標記為存活,並清除 Dirty Card 標記,減輕 Final Remark 壓力。

Phase 4:Concurrent Abortable Preclean(可中斷並發預清理)

  • 反覆做 Preclean 工作,直到觸發終止條件(如迭代次數、耗時、年輕代 Eden 區使用率等)。
  • 目標:在不阻塞用戶線程的前提下,盡可能多完成 Final Remark 的工作,縮短後續 STW 時間。
  • 示例中用戶時間 0.167s,時鐘時間 1.074s,說明 GC 線程大量等待,主動延長此階段以換取更短的 Final Remark。

Phase 5:Final Remark(重新標記,STW)

  • 第二次也是最後一次 STW,完成全老年代存活對象的最終標記(含並發階段漏標/新建對象)。
  • 子階段:Rescan (parallel) 並行重掃、weak refs processing 處理弱引用、class unloading 卸載類、scrub symbol/string table 清理符號/字符串表。
  • 日誌關鍵:YG occupancy 年輕代佔用、CMS-remark 老年代標記後佔用、11200006K(12514816K) 整堆標記後佔用。

Phase 6:Concurrent Sweep(並發清除)

  • 回收未被標記的對象內存,更新 Free Lists,無需 STW。

Phase 7:Concurrent Reset(並發重置)

  • 重置 CMS 內部數據結構,為下一輪循環做準備。

四、調優建議與常見坑點

  1. 關注晉升量:Minor GC 後老年代增長過快,檢查年輕代大小、對象分配速率、大對象直接進老年代情況。
  2. 縮短 Final Remark:適當調大 -XX:CMSMaxAbortablePrecleanTime、-XX:CMSAbortablePrecleanWaitMillis,或啟用 -XX:+CMSScavengeBeforeRemark 在 Remark 前強制一次 Young GC。
  3. 碎片整理:CMS 不壓縮,長期運行易產生碎片導致 Concurrent Mode Failure 退化為 Serial Old。可定期觸發 -XX:+UseCMSCompactAtFullCollection(需 STW)或設置 CMSFullGCsBeforeCompaction。
  4. 日誌參數推薦:
    -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime
    -XX:+PrintTenuringDistribution -Xloggc:/path/gc.log

五、總結

CMS 通過「分階段並發 + 兩次極短 STW」實現了低延遲目標,但也帶來了碎片化、CPU 競爭、浮動垃圾等副作用。深度理解七大階段的日誌含義、準確計算晉升量、針對性調優 Preclean/Remark 參數,是駕馭 CMS 的關鍵。隨著 G1、ZGC、Shenandoah 的成熟,新項目建議優先評估新一代低延遲收集器;存量系統若仍在用 CMS,做好日誌監控與碎片整理策略同樣至關重要。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved