protected void finalize() throws Throwable 是 Object 定義的方法,GC 在回收物件前嘗試呼叫。類似 C++ 解構函數,但語意與保證截然不同。
finalize()finalize() 完成 → 物件再次標記為可回收結果:佔用記憶體時間延長 2+ 倍,Finalizer 佇列堆積易導致 OutOfMemoryError。
finalize() 拋出異常會靜默吞噬,導致物件「復活」但資源未釋放finalize() 執行順序無保證finalize()(除非 System.runFinalizersOnExit(true),已過時)public class OOMFinalizer {
// finalize 中呼叫 yield(),模擬耗時/阻塞
@Override
protected void finalize() throws Throwable {
Thread.yield(); // 放棄 CPU,Finalizer 執行緒極慢
}
public static void main(String[] args) {
while (true) {
new OOMFinalizer(); // 瘋狂建立物件
// Finalizer 執行緒跟不上 → 佇列爆炸 → Heap OOM
}
}
}
此程式在數秒內拋出 java.lang.OutOfMemoryError: Java heap space。
Joshua Bloch (Effective Java):「
finalize()是不可預測、危險、通常不必要的。請使用try-with-resources或AutoCloseable。」
| 場景 | finalize() 表現 | 推薦替代 |
|---|---|---|
| 檔案/資料庫連線關閉 | 可能不執行 → 洩漏 | try (Resource r = ...) { } |
| 原生記憶體釋放 (JNI) | 延遲不可控 | Cleaner / PhantomReference |
| 快取/登記表移除 | 順序不可控 | 明確 remove() 呼叫 |
| 關鍵業務邏輯 | 極度危險 | 絕對禁止 |
public class ResourceHandler implements AutoCloseable {
private static final Cleaner CLEANER = Cleaner.create();
private final Cleaner.Cleanable cleanable;
private final NativeResource resource; // 假設的原生資源
public ResourceHandler() {
this.resource = NativeResource.allocate();
// 註冊清理動作,比 finalize() 更可控
this.cleanable = CLEANER.register(this, () -> {
resource.release();
System.out.println("Cleaner 執行釋放");
});
}
@Override
public void close() {
cleanable.clean(); // 立即執行清理
}
// 使用端
public static void main(String[] args) {
try (ResourceHandler h = new ResourceHandler()) {
// 業務邏輯
} // 確定性釋放
// 若忘記 close(),GC 時 Cleaner 仍會執行(但不保證時機)
}
}
Cleaner 優勢:
clean() 立即釋放ReferenceQueue<Resource> queue = new ReferenceQueue<>();
PhantomReference<Resource> ref = new PhantomReference<>(resource, queue);
// 專用執行緒輪詢佇列
new Thread(() -> {
while (true) {
PhantomReference<Resource> r = (PhantomReference<Resource>) queue.remove();
// 此時物件確定將被回收,安全執行清理
nativeFree(r.get()); // 需保留原生指標
}
}).start();
finalize() 是 Java 早期設計遺留的「反模式」:承諾釋放資源,實則不可靠、高開銷、易致命。現代 Java 開發鐵律:
try-with-resources / AutoCloseable(確定性、即時、異常安全)Cleaner (JDK 9+) 或 PhantomReference(可控、非阻塞)finalize() 中寫業務邏輯理解 finalize() 的缺陷,是從「會用 Java」進階到「懂 JVM 記憶體模型」的關鍵一步。