Object.finalize() 作為 Java 早期提供的「析構函數」機制,長期被誤解與濫用。本文從 JVM 內部實現角度,揭示 FinalReference、Finalizer、ReferenceQueue、FinalizerThread 協作全貌,並論證為何應避免使用及最佳替代方案。
Reference,專為含非空 finalize() 的對象服務。newInstance)時檢測:若類覆寫了 finalize() 且方法體非空,自動將對象封裝為 FinalReference 並註冊到 Finalizer。/* Invoked by VM */
static void register(Object finalizee) {
new Finalizer(finalizee);
}
JVM 在特定時機(對象首次可能被 GC 回收前)調用,創建 Finalizer 實例並加入未執行隊列。
private Finalizer(Object finalizee) {
super(finalizee, queue); // 關聯 ReferenceQueue
add(); // 加入雙向鏈表,供 shutdown 時調用
}
queue:ReferenceQueue 實例,GC 將可回收對象的引用入隊。add():維護 unfinalized 鏈表,System.runFinalizersOnExit 控制退出時是否執行。當 GC 判定對象僅被 FinalReference 引用(即無強引用),將該引用入隊。FinalizerThread 從隊列取出即表示對象「準備好被 finalize」。
private static class FinalizerThread extends Thread {
public void run() {
for (;;) {
try {
Finalizer f = (Finalizer) queue.remove();
f.runFinalizer(sun.misc.JavaLangAccess.getJavaLangAccess());
} catch (InterruptedException x) { /* ignore */ }
}
}
}
線程屬性:系統線程組、優先級 MAX_PRIORITY-2、守護線程。JVM 啟動時由 Finalizer 靜態塊自動啟動。
synchronized (this) {
if (hasBeenFinalized()) return;
remove(); // 從 unfinalized 移除,設置 next=this 標記已執行
}
try {
Object finalizee = this.get();
if (finalizee != null && !(finalizee instanceof java.lang.Enum)) {
jla.invokeFinalize(finalizee); // 反射調用 finalize()
finalizee = null; // 減少誤保留
}
} catch (Throwable x) { /* 吞噬異常,防止線程終止 */ }
super.clear(); // 清除引用,允許對象真正被 GC 回收
| 特性 | 說明 | 風險 |
|---|---|---|
| 單次執行保證 | hasBeenFinalized() + remove() 確保最多執行一次 | 異常導致未執行也不會重試 |
| 異常吞噬 | catch (Throwable) 吞噬所有異常 | 錯誤靜默失敗,極難排查 |
| 無執行時間保證 | 不限制 finalize() 耗時 | 慢 finalize 阻塞整個回收鏈,導致 OOM |
| 執行順序不定 | 取決於 GC 發現順序與隊列順序 | 無法依賴順序釋放資源 |
| 對象復活 | finalize() 中重新建立強引用可「復活」對象 | 復活對象不再有 finalize 機會,極易洩漏 |
FinalizerThread 單線程串行,成為瓶頸。public class ResourceHolder implements AutoCloseable {
private final Socket socket;
public ResourceHolder(Socket s) { this.socket = s; }
@Override public void close() throws IOException {
socket.close(); // 確定性釋放
}
}
// 使用
try (ResourceHolder holder = new ResourceHolder(socket)) {
// 業務邏輯
} // 自動調用 close(),即使拋異常也執行
public class CleanerDemo implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private final Cleaner.Cleanable cleanable;
private final Resource resource;
public CleanerDemo(Resource res) {
this.resource = res;
this.cleanable = cleaner.register(this, () -> {
// 清理邏輯,運行於 Cleaner 專用線程
res.release();
});
}
@Override public void close() { cleanable.clean(); }
}
clean() 或配合 try-with-resources 確保及時性。用於精確感知對象被 GC 回收時機,配合 ReferenceQueue 異步處理清理,完全替代 finalize 語義且無性能負擔。
finalize() 是 JVM 早期設計的歷史包袱,其機制雖精巧但缺陷顯著。現代 Java 開發應堅持「確定性資源管理」原則:優先 try-with-resources,輔以 Cleaner 處理非關鍵資源清理,徹底拒絕 finalize()。