Java 堆是執行期資料區,所有類實例(物件)均在此分配空間。物件透過 new、newarray 等指令建立,無需程式碼顯式釋放,而是由 JVM 的垃圾回收器(GC)自動管理。本文系統梳理 GC 的意義、核心演算法、監控手段及實戰注意事項。
所有 GC 演算法核心任務皆為:識別無用物件 → 回收其佔用記憶體。主流演算法以「根集合」為起點,判定物件可達性:
| 演算法 | 核心機制 | 優缺點 |
|---|---|---|
| 引用計數法 | 每物件維護計數器,引用+1,失效-1,歸零即回收 | 執行快、無長停頓,適合即時系統;但無法處理循環引用,且每次賦值需更新計數器,開銷大 |
| 標記-清除 | 從根集掃描標記可達物件,清除不可達者 | 解決循環引用;但會產生記憶體碎片,分配大物件困難 |
| 壓縮演算法 | 清除後將存活物件移至堆一端,形成連續空閒區 | 解決碎片;需更新所有引用,引入句柄表增加間接開銷 |
| 複製演算法 | 堆分物件區與空閒區,GC 時將存活物件複製到空閒區,角色互換 | 無碎片、分配快;但浪費一半空間,Stop-the-world 導致停頓 |
| 分代演算法 | 依物件存活時間分代(新生代/老年代),高頻回收新生代 | 符合「多數物件朝生夕死」規律,大幅降低全堆掃描頻率,現代 JVM 主流選擇 |
| 自適應演算法 | 運行時監控堆狀態,動態選擇最適演算法 | 理論最優;實現複雜度高 |
java -verbose:gc TestGC
# 輸出範例:[Full GC 168K->97K(1984K), 0.0253873 secs]
# 箭頭前後為 GC 前後存活物件大小,括號內為堆總容量,末尾為耗時
finalize() 機制與誤區finalize() 釋放非 Java 資源(如 JNI 分配的 C 記憶體)。finalize() 做常規清理:調用時機不確定、可能導致物件復活、拋出異常會中斷終結流程。應提供顯式 close()/dispose() 方法,配合 try-with-resources 使用。System.gc() 僅是建議,JVM 可忽略。null;若物件被監聽,先移除監聽器再置空。深入理解堆與 GC,不僅能排查 OOM、長停頓等疑難雜症,更能指導寫出高效能、低延遲的 Java 應用。