在 JVM 中,垃圾收集(Garbage Collection, GC)算法的核心目標是自動回收無用對象的內存,同時盡量減少對應用性能的影響。以下是 JVM 中主要垃圾收集算法的原理、流程及實際應用場景的詳細介紹:
一、標記-清除算法(Mark-Sweep)
原理
- 標記階段:從 GC Roots(如棧引用、靜態變量)出發,遍歷對象圖,標記所有存活對象。
- 清除階段:掃描堆內存,回收未被標記的對象所占用的內存(直接釋放,不整理內存)。
工作流程
- 暫停所有應用線程(STW,Stop-The-World),啟動標記。
- 標記完成后,清除未標記的垃圾對象。
- 恢復應用線程。
優點
- 實現簡單,內存回收效率較高。
缺點
- 內存碎片化:清除后會產生不連續的內存空間,可能無法分配大對象,最終觸發 Full GC。
- 兩次 STW:標記和清除階段均需暫停應用線程。
應用場景
- CMS 收集器的老年代回收(僅清除階段,標記階段并發執行)。
二、標記-整理算法(Mark-Compact)
原理
- 標記階段:與標記-清除相同,標記所有存活對象。
- 整理階段:將所有存活對象向內存一端移動,清理邊界外的內存(消除碎片)。
工作流程
- STW 標記存活對象。
- 將存活對象移動到內存一端,保持連續。
- 清除邊界外的內存。
- 恢復應用線程。
優點
- 無內存碎片:整理后內存連續,適合長期運行的應用。
- 空間利用率高:減少大對象分配失敗的概率。
缺點
- 兩次 STW 且時間長:移動對象需要額外時間,尤其是堆較大時。
- 內存移動開銷:對象復制可能影響吞吐量。
應用場景
- Serial Old、Parallel Old 收集器的老年代回收。
- G1 的混合回收階段(復制存活對象到新 Region)。
三、復制算法(Copying)
原理
- 將堆內存分為兩塊(From 和 To 空間),每次只使用其中一塊。
- 存活對象復制:將 From 空間的存活對象復制到 To 空間,并保持緊湊排列。
- 清空 From 空間:復制完成后,直接清空 From 空間。
工作流程
- STW 標記 From 空間的存活對象。
- 將存活對象按順序復制到 To 空間。
- 交換 From 和 To 空間的角色。
- 恢復應用線程。
優點
- 無碎片:對象在 To 空間連續排列。
- 高效回收:只需遍歷存活對象,適合存活率低的場景。
缺點
- 內存浪費:需預留一半內存作為復制空間,實際可用內存僅 50%。
- 存活率高時效率低:若多數對象存活,復制成本高。
應用場景
- 年輕代回收(如 Serial、ParNew、Parallel Scavenge 收集器)。
- G1 的年輕代 Region 回收。
四、分代收集算法(Generational Collection)
核心思想
基于對象的生命周期特性,將堆劃分為不同區域(年輕代、老年代),對不同代采用不同算法:
- 年輕代:對象存活率低,使用復制算法。
- 老年代:對象存活率高,使用標記-清除或標記-整理算法。
分代依據
- 弱分代假說(Weak Generational Hypothesis):絕大多數對象朝生夕死。
- 強分代假說(Strong Generational Hypothesis):多次存活的對象傾向于繼續存活。
工作流程
- 年輕代 Minor GC:頻繁觸發,使用復制算法回收 Eden 和 Survivor 區。
- 老年代 Major GC/Full GC:觸發頻率低,使用標記-清除或標記-整理算法。
應用場景
- 所有現代 JVM 收集器的默認策略(如 CMS、G1、ZGC 等)。
五、增量收集算法(Incremental Collecting)
原理
- 將垃圾回收過程分為多個小步驟,與應用線程交替執行。
- 每次僅回收部分垃圾,減少單次 STW 時間。
優點
- 降低延遲:每次暫停時間短,適合實時性要求高的場景。
缺點
- 總體吞吐量下降:頻繁切換線程導致額外開銷。
- 實現復雜:需處理應用線程與回收線程的交互。
應用場景
- CMS 的并發標記階段(并發執行,但需最終 STW 修正)。
- Shenandoah 收集器(并發復制對象)。
六、分區收集算法(Region-Based)
原理
- 將堆劃分為多個獨立區域(Region),優先回收垃圾比例高的區域(Garbage-First 原則)。
- 結合分代思想,動態分配區域角色(Eden、Survivor、Old)。
優點
- 可預測停頓:通過控制每次回收的區域數量,滿足
MaxGCPauseMillis
目標。 - 高效管理大堆:避免全堆掃描,減少 STW 時間。
應用場景
- G1 收集器:分 Region 管理,混合回收年輕代和老年代。
- ZGC/Shenandoah:基于類似思想,擴展為全堆并發回收。
七、算法對比與選型
算法 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
標記-清除 | 實現簡單,無移動開銷 | 內存碎片,兩次 STW | 老年代(CMS) |
標記-整理 | 無碎片,空間利用率高 | STW 時間長,移動開銷大 | 老年代(Serial Old) |
復制 | 無碎片,回收效率高 | 內存浪費,存活率高時低效 | 年輕代(所有分代收集器) |
分代收集 | 結合對象生命周期優化 | 需維護分代結構 | 所有現代收集器 |
增量收集 | 低延遲 | 吞吐量下降 | 實時系統(Shenandoah) |
分區收集 | 可預測停頓,大堆友好 | 元數據管理復雜 | G1、ZGC、Shenandoah |
總結
JVM 的垃圾收集算法是內存管理的核心,不同算法在吞吐量、延遲和內存開銷之間權衡。實際應用中,分代收集與分區算法(如 G1)成為主流,而 ZGC/Shenandoah 進一步通過并發和壓縮技術突破停頓時間限制。選擇算法需結合應用場景(如堆大小、延遲要求)及 JVM 版本特性。
以下以CMS和G1垃圾收集器的原理與工作流程舉例:
CMS(Concurrent Mark-Sweep)收集器
設計目標
- 低延遲:通過并發標記和清除減少停頓時間(STW),適用于對響應時間敏感的應用(如Web服務)。
- 老年代專用:僅管理老年代,需與年輕代收集器(如ParNew)配合使用。
核心原理
- 標記-清除算法
- 通過標記存活對象,直接清除未標記的垃圾對象,不進行內存整理,可能導致內存碎片。
- 并發執行
- 大部分垃圾回收階段與應用線程并發執行,減少STW時間。
工作流程(4個階段)
-
初始標記(Initial Mark)
- STW:短暫暫停,標記從GC Roots直接可達的對象(如棧引用、靜態變量)。
- 依賴年輕代回收:通常與年輕代的Minor GC同步觸發,避免全堆掃描。
-
并發標記(Concurrent Mark)
- 并發執行:遍歷老年代對象圖,標記所有存活對象(與應用線程并行)。
- 可能漏標:并發階段應用線程可能修改對象引用,需后續修正。
-
重新標記(Remark)
- STW:修正并發標記期間因應用線程運行導致的標記變化(使用增量更新或原始快照算法)。
- 時間較長:需處理所有變更,是CMS中最耗時的STW階段。
-
并發清除(Concurrent Sweep)
- 并發執行:清除未標記的垃圾對象,回收內存空間。
- 不整理內存:清除后產生內存碎片,需定期Full GC(使用Serial Old)進行壓縮。
關鍵問題
- 內存碎片:長期運行后可能觸發Full GC,導致長時間停頓。
- 浮動垃圾:并發階段新產生的垃圾需等到下次回收。
- CPU競爭:并發階段占用CPU資源,可能影響應用吞吐量。
G1(Garbage-First)收集器
設計目標
- 平衡吞吐量與延遲:通過分Region和可預測停頓,適應大堆內存(4GB+)場景。
- 全堆管理:統一管理年輕代和老年代,動態分配Region角色。
核心原理
-
分Region模型
- 堆被劃分為多個大小固定(1MB~32MB)的Region,每個Region可以是Eden、Survivor或Old類型。(堆/2048)
- Humongous區域:存儲大于Region 50%的大對象。
-
標記-整理算法
- 通過復制存活對象到新Region,避免內存碎片。
-
停頓預測模型
- 根據用戶設定的
MaxGCPauseMillis
,優先回收垃圾比例高的Region(Garbage-First原則)。
- 根據用戶設定的
工作流程(3種操作模式)
G1的回收過程分為三種操作:年輕代回收(Young GC)、并發標記周期(Concurrent Marking Cycle) 和 混合回收(Mixed GC)。
1. 年輕代回收(Young GC)
- STW:暫停所有應用線程,復制Eden和Survivor區的存活對象到新Survivor或Old Region。
- 觸發條件:Eden區占滿時觸發。
2. 并發標記周期(Concurrent Marking Cycle)
-
初始標記(Initial Mark)
- STW:標記GC Roots直接可達的對象(與Young GC同步觸發,復用Young GC的暫停)。
-
根區域掃描(Root Region Scan)
- 掃描Survivor區(作為GC Roots的一部分),確保并發標記的正確性。
-
并發標記(Concurrent Mark)
- 并發執行:遍歷堆中所有存活對象,標記存活狀態。
-
最終標記(Final Mark)
- STW:處理SATB(Snapshot-At-The-Beginning)隊列中的引用變更,確保標記準確性。
-
清除階段(Cleanup)
- 部分STW:統計各Region的垃圾比例,識別高價值回收區域。
- 不回收內存:僅選擇待回收的Region,為混合回收做準備。
3. 混合回收(Mixed GC)
- STW:同時回收年輕代和部分老年代Region(根據垃圾比例選擇)。
- 多次執行:可能分多次回收,直到滿足
MaxGCPauseMillis
目標。
關鍵優勢
- 內存整理:通過復制算法避免碎片。
- 可預測停頓:動態調整回收的Region數量以滿足停頓目標。
- 大堆優化:分Region設計適合管理超大堆內存。
CMS與G1對比
特性 | CMS | G1 |
---|---|---|
堆結構 | 傳統分代(年輕代+老年代) | 分Region,邏輯分代 |
算法 | 標記-清除(碎片需Full GC整理) | 標記-整理(復制算法,無碎片) |
停頓控制 | 不可預測 | 通過MaxGCPauseMillis 預測 |
適用場景 | 中小堆,低延遲優先 | 大堆(4GB+),平衡吞吐量和延遲 |
內存開銷 | 低 | 較高(Region元數據) |
Full GC觸發 | 內存不足/碎片時觸發Serial Old | 盡量避免,依賴并發周期 |
JDK支持 | 已廢棄(JDK 14移除) | JDK 9+默認 |
總結
- CMS:通過并發標記-清除減少停頓,適合中小堆且延遲敏感的場景,但面臨碎片和Full GC風險。
- G1:分Region模型和可預測停頓設計,適合大堆內存,平衡吞吐量與延遲,是JDK 9+的默認選擇。