JVM 在堆積區管理所有物件記憶體,不像 C++ 有「自動變量」(棧上分配,離開作用域自動釋放)。Java 所有物件皆在堆上,必須由 GC 自動回收,否則會發生記憶體洩漏導致 OutOfMemoryError。
早期嘗試:被引用 +1,引用消失 -1,歸零即可回收。致命缺陷:無法處理循環引用(A 參考 B,B 參考 A),導致計數永不歸零。
從 GC Roots 出發,沿著引用鏈向下遍歷:
基於弱分代假說:
+--------------------------+
| Heap |
| +--------------------+ |
| | Young Generation | | <- 新生代 (Eden + 2 Survivor)
| | +--------------+ | |
| | | Eden | | | <- 新物件誕生地
| | +--------------+ | |
| | | Survivor 0 | | | <- 任一時刻至少一個 Survivor 為空
| | | Survivor 1 | | |
| | +--------------+ | |
| +--------------------+ |
| | Old Generation | | <- 老年代 (長命物件)
| +--------------------+ |
| | Permanent Gen | | <- JDK 8 前:類別元數據;JDK 8+ 移至 Metaspace (Native Memory)
| +--------------------+ |
+--------------------------+
檔案句柄、Socket、資料庫連線、JNI 記憶體等不受 GC 管理,必須手動釋放。
finalize()// 危險!finalize() 不保證執行、執行時間不定、異常會中斷 finalizer 執行緒
protected void finalize() {
try { nativeResource.close(); } finally { super.finalize(); }
}
// 編譯器自動生成 finally 區塊關閉資源,即使發生異常也保證釋放
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
// 使用資源
} // 自動關閉 rs, stmt, conn
AutoCloseable + 手動管理public class NativeResource implements AutoCloseable {
private long nativeHandle;
public void close() { nativeFree(nativeHandle); } // JNI 呼叫釋放
}
// 使用者必須負責調用 close()
NativeResource res = new NativeResource();
try {
// use
} finally {
res.close();
}
System.gc() 的迷思System.gc(),應透過 JVM 參數調優(如 -Xms, -Xmx, -XX:+UseG1GC 等)。| 收集器 | 適用場景 | 特點 |
|---|---|---|
| G1 GC | 大多數通用應用 (預設) | 區域化、可預測停頓、混合收集、支援超大堆 |
| ZGC | 超低延遲、超大堆 (TB 級) | 次世代、並發整理、停頓 < 1ms、JDK 11+ 實驗/15+ 正式 |
| Shenandoah | 低延遲、大堆 | 並發整理、紅綠指標、OpenJDK 專案、JDK 12+ |
| Serial GC | 單核、小堆、嵌入式 | 單執行緒、Stop-the-world、開銷最小 |
理解 GC 核心原理(根搜尋、分代、安全點、記憶體屏障),比背誦參數更重要。遇到 OOM 時,請先用
jmap -histo:live、jhat、VisualVM、Arthas、JProfiler分析堆轉儲檔,找出真正佔用記憶體的物件與引用鏈。