許多 C++ 開發者轉 Java 時,直覺將 finalize() 視為解構函數。本文透過 JVM 垃圾回收機制、執行時期行為實測,證明兩者本質差異,並建立正確的資源管理認知。
| 特性 | C++ 解構函數 | Java finalize() |
|---|---|---|
| 觸發時機 | 離開作用域 (棧) / delete (堆) — 確定性 | GC 判定物件不可達時 — **非確定性** |
| 執行順序 | 子類 → 父類 (自動鏈式) | **不自動呼叫父類**,需手動 super.finalize() |
| 執行保證 | 一定執行 (除非 terminate()) | **不保證執行** (JVM 關閉前可能不 GC) |
| 例外處理 | 例外導致程式終止 | 例外被 GC 執行緒吞沒,不影響應用 |
| 主要用途 | 釋放所有資源 (記憶體、檔案、鎖、Socket) | 僅作為**最後防線**釋放非 Java 資源 (JNI、Native 句柄) |
finalize(),**第一次 GC 將物件移入 Finalizer 佇列**,由 Finalizer 執行緒稍後執行 finalize()。finalize() 執行完成 → 物件再次變為不可達 → **下一次 GC 才真正釋放記憶體**。finalize() 中重新建立強引用 (物件復活),物件逃脫回收,但**第二次不可達時不再呼叫 finalize()**。class Thing {
static int created = 0, finalized = 0;
Thing() { created++; }
protected void finalize() { finalized++; }
}
public class FinalizeDemo {
public static void main(String[] args) {
while (Thing.finalized < 1) new Thing(); // 建立 1000+ 物件
System.out.println(Thing.created + " created");
System.out.println(Thing.finalized + " finalized");
// 輸出:1000+ created, 0 finalized (程式結束前 GC 未執行)
}
}
即使呼叫 System.gc() 也僅是**建議**,JVM 可能忽略。JDK 1.1 新增 System.runFinalizersOnExit(true) 強制退出時執行,但因執行順序不可控、效能影響大,**已於 JDK 1.2 棄用並移除**。
try (FileInputStream in = new FileInputStream("data.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(in))) {
String line = br.readLine();
// 自動按相反順序 close(),即使拋出例外
}
FileInputStream in = null;
try {
in = new FileInputStream("data.txt");
// 業務邏輯
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
}
public class NativeResource implements AutoCloseable {
private long nativeHandle;
public NativeResource() { nativeHandle = nativeAllocate(); }
public void close() { if (nativeHandle != 0) nativeFree(nativeHandle); nativeHandle = 0; }
protected void finalize() { close(); } // 最後防線
private native long nativeAllocate();
private native void nativeFree(long handle);
}
close(),且無法修改原始碼時,作為保底釋放 Native 句柄。finalize() 記錄堆疊追蹤,開發階段發現未正確關閉的資源。永遠不要依賴 finalize() 釋放關鍵資源。它是非確定性、不可靠、效能負擔的「最後防線」。請採用 try-with-resources 或 try-finally 明確管理資源生命週期,這才是 Java 風格的資源安全保證。