前言
在 Java 虛擬機(JVM)的自動記憶體管理機制下,開發者無需為每個 new 操作編寫配對的 delete/free 代碼。然而,當發生記憶體洩漏或溢出問題時,若不了解虛擬機如何使用記憶體,排查錯誤將變得異常困難。本文將系統梳理 Java 垃圾收集(GC)機制、對象存活判定算法、垃圾收集算法、常見收集器以及記憶體分配與回收策略,幫助開發者建立完整的 JVM 記憶體管理知識體系。
一、對象存活判定算法
GC 在回收堆內存前,首先要確定哪些對象「存活」,哪些已經「死去」。主要有兩種判定算法:
1. 引用計數算法
核心思想: 給對象添加一個引用計數器,每當有一個地方引用它時計數器加 1,引用失效時減 1。計數器為 0 的對象即為不可用對象。
- 優點: 實現簡單,判斷效率高。
- 缺點: 難以解決對象間相互循環引用的問題(如 A 引用 B,B 引用 A,計數器永不為 0)。
2. 可達性分析算法(主流算法)
核心思想: 通過一系列「GC Roots」對象作為起始點,向下搜索形成引用鏈。若對象到 GC Roots 無任何引用鏈相連,則判定為可回收對象。

可作為 GC Roots 的對象包括:
- 虛擬機棧中引用的對象(棧幀中的局部變量表)
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中 JNI 引用的對象
3. 生存還是死亡?—— finalize() 最後的機會
即使在可達性分析中不可達,對象也並非「非死不可」,需經歷兩次標記:
- 第一次標記: 發現無引用鏈後標記並篩選,判斷是否有必要執行
finalize() 方法(未覆寫或已被調用過視為「不必要」)。
- 進入 F-Queue: 若需執行
finalize(),對象被放入 F-Queue,由低優先級的 Finalizer 線程觸發執行(不保證等待結束)。
- 第二次標記: GC 對 F-Queue 中對象進行小規模標記。若對象在
finalize() 中重新與引用鏈建立關聯(如 this 賦值給類變量),則移出「即將回收」集合;否則真正被回收。
注意: finalize() 只會被系統自動調用一次,建議避免使用,改用 try-finally 或 try-with-resources 釋放資源。
二、垃圾收集算法
1. 標記-清除算法
- 過程: 標記所有需回收對象 → 統一回收。
- 不足: 效率較低;產生大量不連續記憶體碎片,可能導致大對象無法分配而提前觸發 GC。
2. 複制算法
- 過程: 將可用記憶體劃分為大小相等的兩塊,每次只用一塊。用完時將存活對象複製到另一塊,再清理已用空間。
- 優點: 實現簡單,運行高效,無碎片問題。
- 代價: 記憶體縮小為原來一半。
3. 標記-整理算法
- 過程: 標記需回收對象 → 讓所有存活對象向一端移動 → 清理端邊界外記憶體。
- 適用: 老年代(對象存活率高、無額外空間擔保)。
4. 分代收集算法(主流商用 JVM 採用)
- 核心思想: 根據對象存活周期劃分記憶體區域,針對不同區域特點選擇適當算法。
- 新生代: 大批對象死去、少量存活 → 採用複制算法(Eden + 兩個 Survivor 區)。
- 老年代: 對象存活率高、無額外空間 → 採用標記-清除或標記-整理算法。
三、HotSpot 實現的分代結構
- 新生代: Eden 空間 + 兩個 Survivor 空間(From/To)。新對象優先在 Eden 分配,Minor GC 後存活對象在 Survivor 間輪換,年齡達閾值(默認 15)晉升老年代。
- 老年代: 存放長期存活對象,空間較大,Major GC(Full GC)頻率低。
四、常見垃圾收集器

連線表示可搭配使用。虛擬機所在區域標識屬於新生代或老年代收集器。
核心概念:Stop-the-World (STW)
任何 GC 算法中都會發生 STW,意味著 JVM 為執行 GC 停止應用程序線程,僅 GC 線程工作。GC 優化核心往往是減少 STW 時間。
主要收集器特點:
| 收集器 | 類型 | 線程 | 算法 | 特點 |
| Serial | 新生代 | 單線程 | 複制 | 簡單高效,Client 模式默認,STW 時間長 |
| ParNew | 新生代 | 多線程 | 複制 | Serial 多線程版,與 CMS 配合 |
| Parallel Scavenge | 新生代 | 多線程 | 複制 | 關注吞吐量,自適應調節 |
| Serial Old | 老年代 | 單線程 | 標記-整理 | Serial 老年代版 |
| Parallel Old | 老年代 | 多線程 | 標記-整理 | Parallel Scavenge 老年代版 |
| CMS | 老年代 | 多線程 | 標記-清除 | 低停頓,並發收集,CPU 敏感,有碎片 |
| G1 | 全區域 | 多線程 | 標記-整理+複制 | 面向服務端,可預測停頓,Region 劃分 |
CMS 收集器四階段:
- 初始標記(STW,快)
- 並發標記(與用戶線程並發)
- 重新標記(STW,修正並發標記變動)
- 並發清除(與用戶線程並發)
G1 收集器四階段:
- 初始標記(STW,修改 TAMS)
- 並發標記(可達性分析)
- 最終標記(STW,合併 Remembered Set Logs)
- 篩選回收(按回收價值排序,制定計劃)
五、記憶體分配與回收策略
- 對象優先在 Eden 分配: Eden 不足觸發 Minor GC。
- 大對象直接進入老年代: 避免在 Eden/Survivor 間大量複製(長字符串、大數組)。
- 長期存活對象進入老年代: 年齡計數器達 MaxTenuringThreshold(默認 15)晉升。
- 動態對象年齡判定: Survivor 中相同年齡對象總大小超過 Survivor 一半,直接晉升老年代。
- 空間分配擔保: Minor GC 前檢查老年代最大連續空間是否大於新生代所有對象總空間,不足則視 HandlePromotionFailure 設置決定是否冒險。
六、總結
記憶體回收與垃圾收集器是影響系統性能、併發能力的關鍵因素。JVM 提供多種收集器和大量調節參數,旨在根據實際應用場景選擇最優方案。沒有固定的最佳收集器組合,沒有萬能的調優參數,唯有結合業務特性、監控數據、壓測結果,持續觀測與調整,才能榨乾硬件性能。
參考資料:《深入理解 Java 虛擬機》(周志明)