Effective Java 第三版讀書筆記——條款8:避免使用 Finalizer 和 Cleaner 機制
Finalizer 機制通常是不可預知的、危險的、不必要的。它們的使用會導致不穩定的行為,糟糕的效能和移植性問題。從 Java 9 開始,Finalizer 機制已被棄用,但仍被 Java 類庫所使用。 Java 9 中 Cleaner 機制代替了 Finalizer 機制。 Cleaner 機制不如 Finalizer 機制那樣危險,但仍然是不可預測、執行緩慢並且通常是不必要的。
Finalizer 和 Cleaner 機制的缺點:
-
不能保證他們能夠及時執行。 從一個物件變得無法訪問到 Finalizer 和 Cleaner 機制開始執行的這段時間是任意長的。 這意味著你永遠不應該用 Finalizer 和 Cleaner 機制做任何時間敏感(time-critical)的事情。Java 規範不能保證 Finalizer 和 Cleaner 機制能及時執行;它甚至不能保證它們是否會執行。
-
Finalizer 機制的另一個問題是在執行 Finalizer 機制過程中,未捕獲的異常會被忽略,並且該物件的 Finalizer 機制也會終止。未捕獲的異常會使其他物件陷入一種損壞的狀態(corrupt state)。如果另一個執行緒試圖使用這樣一個損壞的物件,可能會導致任意不確定的行為。通常情況下,未捕獲的異常將終止執行緒並列印堆疊跟蹤( stacktrace),但如果發生在Finalizer機制中,這些異常甚至不會列印警告資訊。Cleaner 機制沒有這個問題,因為使用 Cleaner 機制的類庫可以控制其執行緒。
-
嚴重的效能損失。 通常來說,使用 finalizer 機制建立和銷燬物件要慢大約 50 倍。這主要是因為 finalizer 機制會阻礙有效的垃圾收集。
-
嚴重的安全問題:它們會開啟你的類來進行 finalizer 機制攻擊。
Finalizer 和 Cleaner 機制的兩個合法用途:
-
作為一個安全網(safety net),以防資源的擁有者忘記呼叫
close
方法。雖然不能保證 Finalizer 和 Cleaner 機制會及時執行(或者是否執行),但是將資源晚一點釋放也要好過永遠不釋放。 -
第二種合理使用Cleaner機制的方法與本地對等類(native peers)有關。本地對等類是一個由普通物件委託的本地(非 Java)物件。由於本地對等類不是普通的 Java 物件,所以垃圾收集器並不知道它,當它的 Java 對等物件被回收時,本地對等類也不會被回收。假設效能是可以接受的,並且本地對等類沒有持有關鍵的資源,那麼 Finalizer 和 Cleaner 機制可能是這項任務的合適的工具。
Cleaner 機制使用起來有點棘手。下面是演示該功能的一個簡單的 Room
類。假設 Room
物件必須在被回收前清理乾淨。Room
類實現 AutoCloseable
介面;它的自動清理安全網使用的是 Cleaner 機制,這僅僅是一個實現細節。與 Finalizer 機制不同,Cleaner 機制不會汙染一個類的公開API:
// An autocloseable class using a cleaner as a safety net
public class Room implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
// Resource that requires cleaning. Must not refer to Room!
private static class State implements Runnable {
int numJunkPiles; // Number of junk piles in this room
State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}
// Invoked by close method or cleaner
@Override
public void run() {
System.out.println("Cleaning room");
numJunkPiles = 0;
}
}
// The state of this room, shared with our cleanable
private final State state;
// Our cleanable. Cleans the room when it’s eligible for gc
private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) {
state = new State(numJunkPiles);
cleanable = cleaner.register(this, state);
}
@Override
public void close() {
cleanable.clean();
}
}