1. 程式人生 > >Effective Java 第三版讀書筆記——條款8:避免使用 Finalizer 和 Cleaner 機制

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();
    }
}