優秀的 Java 開發者必須理解 GC 運作機制、調優手段與有限交互方式。特別是嵌入式、即時系統等對效能敏感的場景,記憶體管理效率直接決定應用成敗。本文系統梳理 GC 基礎原理、增量式 GC、finalize 陷阱、四大引用類型應用,並給出六條編碼建議。
一、GC 基礎原理:有向圖與可達性分析
Java 記憶體管理本質是物件管理:分配用 new,釋放靠 GC。GC 採用有向圖記錄堆中所有物件引用關係:
- GC Roots:棧框局部變數、靜態欄位、JNI 參考等。
- 從 Roots 出發遍歷,能到達的物件為可達,否則為不可達。
- 不可達物件佔用的記憶體將被回收。
Java 規範未強制規定回收演算法、觸發時機,不同 JVM 實現差異大,帶來不確定性。
二、增量式 GC:以吞吐換低停頓
標準 GC 執行時應用程式完全暫停(Stop-the-World)。增量式 GC 將長時間停頓拆解為多次短停頓,降低最大停頓時間,犧牲整體吞吐率。
- HotSpot 啟用參數:
-Xincgc
- 採用 Train GC 演算法:將堆按建立/使用情況分組(車廂),優先回收最舊、關聯最少的車廂。
- 適用場景:即時遊戲、低延遲交易系統、嵌入式裝置。
三、finalize():雙面刃,慎用
protected void finalize() throws Throwable {
try {
// 釋放資源
} finally {
super.finalize(); // 必須手動鏈式呼叫
}
}
三大缺陷:
- GC 需額外追蹤覆寫 finalize 的物件,增加開銷。
- finalize 執行中物件可能「復活」(變可達),GC 需二次掃描。
- 呼叫時機不可預期,資源釋放無保障。
正確姿勢: 程式主動管理資源(try-with-resources、finally),finalize 做最後防線(雙重保險),不可單獨依賴。
四、四大引用類型:與 GC 精準互動
java.lang.ref 包提供引用強度階梯:
| 引用類型 | 強度 | 回收時機 | 典型用途 |
| Strong Reference | 最強 | 永不主動回收 | 一般物件持有 |
| SoftReference | 較強 | 記憶體不足前 | 圖片快取、記憶體敏感快取 |
| WeakReference | 弱 | 下次 GC 必回收 | WeakHashMap、監聽器快取 |
| PhantomReference | 最弱 | finalize 後、回收前 | 資源清理輔助、精確追蹤回收時機 |
SoftReference 快取範例
SoftReference<Image> sr = new SoftReference<>(image);
image = null; // 釋放強引用
// 下次使用
Image img = sr.get();
if (img == null) {
img = loadImage(); // 重新載入
sr = new SoftReference<>(img);
}
記憶體充足時保留,OOM 前自動清理,實現「最大化利用記憶體且不 OOM」。
WeakReference 適用 Map 結構
WeakHashMap 鍵為弱引用,鍵無強引用時自動移除條目,避免記憶體洩漏。
五、六條 GC 友善編碼建議
- 盡早釋放無用引用:複雜物件圖(陣列、樹、圖)手動置 null,加速可達性分析。
- 少用 finalize:改用 try-with-resources、Cleaner (Java 9+)。
- 圖片/大物件用 SoftReference:實現記憶體敏感快取。
- 警惕集合與靜態欄位:全域集合、靜態 Map 易造成「懸掛引用」,定期清理或用弱引用。
- 適時手動
System.gc():空閒期提示 GC,但不保證執行;配合增量式 GC 降低停頓。
- 關注物件存活率:大量短命物件觸發頻繁 Minor GC;長命物件過早晉升老年代導致 Full GC。調整
-Xmn、-XX:TargetSurvivorRatio 等參數優化分代佈局。
結語
GC 不是黑盒。理解可達性分析、分代假設、引用類型語意,才能寫出記憶體友善的程式碼。調優核心三步走:減少分配 → 縮短存活 → 選對收集器。下一篇將深入 G1、ZGC、Shenandoah 低延遲收集器實戰配置。
- 上一篇文章: 減少 Java 臨時物件建立,從設計階段提升效能
- 下一篇文章: Java break 與 continue 完整指南:標籤跳轉、巢狀迴圈控制與最佳實務
Copyright ©
程式碼之家 All Rights Reserved