C++ 程序員需手動 delete,稍有疏忽即導致記憶體洩漏;Java 程序員則將此重擔交給 JVM 垃圾收集器 (Garbage Collector, GC)。本文系統梳理 GC 的核心判定算法、觸發時機、代碼干預手段、finalize 陷阱,助你理解記憶體管理的底層邏輯,寫出更友善於 GC 的代碼。
Java 採用 可達性分析 而非單純引用計數 (無法解決循環引用):
「引用計數」僅作為理解輔助:一個物件無任何引用變數指向時,確實不可達。但循環引用 (A→B→A) 導致計數永不歸零,故生產級 GC 全面採用可達性分析。
| 引用類型 | 強度 | 回收時機 | 典型場景 |
|---|---|---|---|
| 強引用 | 最強 | 永不回收 (OOM 前) | new Object() 一般物件 |
軟引用 SoftReference | 次強 | 記憶體不足前 | 快取、圖片緩存 |
弱引用 WeakReference | 較弱 | 下次 GC 時 | WeakHashMap、監聽器弱引用 |
虛引用 PhantomReference | 最弱 | 隨時 (配合 ReferenceQueue) | 直接記憶體釋放追蹤、精確 finalize 替代 |
| 收集器 | 區域 | 算法 | 特點 | 適用場景 |
|---|---|---|---|---|
| Serial | 新/老 | 複製/整理 | 單執行緒、Stop-The-World | Client 模式、小堆 |
| ParNew | 新 | 複製 | Serial 多執行緒版 | 配合 CMS |
| Parallel Scavenge | 新 | 複製 | 吞吐量優先、自適應調節 | 批次處理、科學計算 |
| CMS | 老 | 標記-清除 | 低延遲、併發標記/清除 | Web 服務、響應敏感 |
| G1 (JDK 7u4+) | 全堆 | 分區標記-整理 | 可預測停頓、區域化、混合收集 | 大堆 (6G+)、綜合場景 |
| ZGC (JDK 11+) | 全堆 | 染色指標、併發整理 | 亞毫秒停頓、TB 級堆 | 超大堆、低延遲要求 |
| Shenandoah (JDK 12+) | 全堆 | 併發整理、轉發指標 | 併發整理、停頓與堆大小無關 | 大堆、低延遲 |
System.gc(); // 僅通知 JVM「適合 GC 了」,不保證立即執行、不保證 Full GC
生產環境極少主動調用,依賴 JVM 人因工程調優 (參數 -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200 等)。
@Deprecated,推薦 try-with-resources / AutoCloseable / Cleaner (JDK 9+) 進行確定性資源釋放。ByteBuffer.allocateDirect)。WeakHashMap、Guava Cache 自動清理。| 誤區 | 事實 |
|---|---|
| GC 只回收沒用的物件 | GC 回收不可達物件;「沒用」是業務邏輯概念,GC 不懂業務。 |
| 調用 System.gc() 立即釋放記憶體 | 僅建議,JVM 可能忽略;且 Full GC 耗時長,反而可能加劇停頓。 |
| finalize 類似 C++ 解構函數 | 解構函數確定性執行;finalize 非確定性、單執行緒、易丟失異常。 |
| 弱引用物件下次 GC 必回收 | 僅當僅有弱引用指向時;若同時有強引用則不回收。 |
| 設 null 就能立即回收 | 斷開強引用僅使物件符合回收資格,實際回收時間由 GC 決定。 |
Java GC 經歷了從單執行緒 Serial 到分代 Parallel/CMS,再到分區 G1、併發整理 ZGC/Shenandoah 的演進,核心目標始終是:在有限硬體資源下,以最小應用停頓換取最大吞吐量。作為開發者,無需微操 GC,但必須理解可達性判定、分代假設、引用強度、收集器特性,並在代碼層面踐行「朝生夕死、縮短作用域、確定性釋放資源」原則,才能與 GC 協作共舞,構建高效穩定的 Java 應用。