GC 算法演進:從理論到工程落地
| 算法 | 核心思想 | 優點 | 缺點 | 典型應用 |
| 引用計數 | 對象頭計數器,歸零即回收 | 即時性、無 STW | 無法處理循環引用 | Python、COM、早期 JVM 嘗試 |
| 標記-清除 | 標記存活,掃描清理未標記 | 實現簡單 | 內存碎片、兩次掃描 | 早期 JVM、CMS 老年代 |
| 複製 | 劃分 From/To 區,存活對象遷移 | 無碎片、分配指針碰撞極快 | 空間利用率 50% | 新生代、Serial/Parallel Scavenge |
| 標記-整理 | 標記後向一端壓縮 | 無碎片、空間利用率高 | 需移動對象、更新引用、STW 長 | 老年代、Serial Old、Parallel Old |
| 分代收集 | 按對象壽命分代,不同代用不同算法 | 綜合上述優點,針對性強 | 實現複雜、需跨代引用處理 | 所有現代商用 JVM (HotSpot, OpenJ9, Zing) |
HotSpot 分代佈局與對象流動
+------------------+------------------+------------------+
| Young | Old | Metaspace |
| (Eden + S0 + S1)| (Tenured) | (Class Metadata)|
+------------------+------------------+------------------+
Minor GC Major/Full GC 類卸載 (少見)
高頻、快、回收率高 低頻、慢、STW 長 元數據區
對象晉升路徑:Eden → Survivor (S0/S1 輪換) → 年齡達閾值 (默認 15) → Old。大對象 (-XX:PretenureSizeThreshold) 直接入 Old。
三大收集器家族:特性與選型決策樹
| 收集器 | 線程模型 | 適用區 | 算法 | 目標 | 適用場景 |
| Serial | 單線程 | Young/Old | 複製/標記-整理 | 低延遲(小堆) | Client 模式、單核、堆 < 100MB、嵌入式 |
| Parallel (Throughput) | 多線程並行 | Young/Old | 複製/標記-整理 | 高吞吐 | 批處理、科學計算、後台任務、多核、對停頓不敏感 |
| CMS (Concurrent Mark Sweep) | 並發+少量 STW | Old | 標記-清除 | 低延遲 | Web 服務、API 网关、對響應時間敏感、堆 < 4GB (JDK 9 棄用, 14 移除) |
| G1 (Garbage First) | 並發+分區並行 | Young/Old 統一 | 標記-整理+複製(區級) | 可預測停頓 | 6GB+ 大堆、微服務、混合負載、JDK 9+ 默認 |
| ZGC / Shenandoah | 並發+讀屏障/轉發指針 | 全堆 | 標記-整理(並發) | 超低延遲 (ms 級) | 超大堆 (TB 級)、實時系統、低延遲交易、JDK 11/12+ |
GC 關鍵參數速查表
# 堆基礎
-Xms4g -Xmx4g # 固定堆避免震盪
-Xmn1536m # 新生代固定 1.5G (G1 不建議固定)
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
# 收集器選擇 (三選一)
-XX:+UseSerialGC
-XX:+UseParallelGC -XX:ParallelGCThreads=8
-XX:+UseG1GC # JDK 9+ 默認
-XX:+UseZGC # JDK 11+ 實驗/生產
# G1 關鍵調優
-XX:MaxGCPauseMillis=200 # 目標停頓 200ms (G1 會自動調整 Young/Old 比例)
-XX:G1HeapRegionSize=16m # 區大小 1-32MB,堆/2048 推算
-XX:InitiatingHeapOccupancyPercent=45 # 老年代佔比 45% 觸發並發標記
# 並行/吞吐權衡 (Parallel)
-XX:GCTimeRatio=19 # 吞吐目標 5% GC 時間 (1/(1+19))
-XX:MaxGCPauseMillis=100 # 停頓目標 (作為參考)
# 診斷輸出
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xlog:gc*:file=gc.log:time,uptime,level,tags
實戰調優四步法
- 基線建立:壓測記錄吞吐、P99 延遲、GC 頻率/停頓、Heap 使用率曲線。
- 瓶頸定性:
- Young GC 頻繁、耗時長 → 增大
-Xmn、調大 SurvivorRatio、檢查大對象直入老年代。
- Old GC 頻繁/Full GC → 檢查晉升過快、內存洩漏、
Metaspace 滿、System.gc() 調用。
- 停頓尖刺 → 檢查安全點、類卸載、JNI 關鍵區、大對象分配失敗觸發 Full GC。
- 參數微調:單一變量原則,每次調整一個關鍵參數,壓測對比。
- 持續觀測:生產環境接入 GC 日誌分析平台,設置告警(如 Full GC > 0/時、停頓 > SLA)。
常見誤區辟謠
- ❌ 「調大堆就能減少 GC」 → 大堆意味著更長的 Full GC 停頓,需配合 G1/ZGC 分區並發。
- ❌ 「CMS 是低延遲銀彈」 → 碎片導致 Full GC 退化為 Serial Old,且 JDK 已移除。
- ❌ 「
System.gc() 能強制回收」 → 僅建議,可能觸發 Full GC,生產禁用 (-XX:+DisableExplicitGC),改用 ExplicitGCInvokesConcurrent。
- ✅ 「對象分配率決定 Young GC 頻率」 → 降低分配率 (對象池、避免大字符串拼接) 比調 GC 參數更有效。
新時代選型建議 (JDK 17/21 LTS)
| 場景 | 首選 | 備選 | 理由 |
| 通用微服務、Web API | G1 (默認) | ZGC (JDK 21+ 生產就緒) | 開箱即用、可預測停頓、大堆友好 |
| 超大堆 (32GB+)、嚴格 SLA | ZGC | Shenandoah | 亞毫秒停頓、並發整理、無分代假設 |
| 批處理、ETL、離線計算 | Parallel | G1 | 極致吞吐、無響應時間要求 |
| 小堆 (< 4GB)、單核/嵌入式 | Serial | G1 | 開銷最小、無線程協調成本 |
總結
GC 調優本質是在分配率、存活率、堆大小、停頓預算、CPU 核心數多維約束下的平衡藝術。理解分代假設、掌握收集器內核機制、建立可觀測體系、遵循「單一變量、壓測驗證」科學方法,才能從「玄學調參」走向「工程調優」。