07-避免使用終結方法

本條目大意

儘量避免在類中使用終結(finalize)方法,在裏面寫一些釋放類中資源的語句。


爲什麼要避免使用 finalize方法?

1、java語言規範不僅不保證 finalize方法會被及時地執行,而且根本不保證他們會被執行。
2、System.gc 和 System.runFinalization 這兩個方法只是增加了finalizer 方法被執行的機會。
3、唯一能保證 finalize 方法被執行的方法有兩個,System.runFinalizersOnExit 和 Runtime.runFinalizersOnExit ,但是這兩個方法已經被廢棄。
4、覆蓋並使用終結方法會有嚴重的性能損失。
5、及時地執行終結方法是垃圾回收算法的一個主要功能,但是在不同的JVM實現中會大相庭徑,使用終結方法可能會喪失平臺無關性。


如果類的對象中封裝的資源確實需要進行釋放,我們應該怎麼做呢?

一般來說,需要釋放資源的有線程或者文件還有涉及到本地資源的對象。

我們不去覆蓋 finalize 方法,而是自己提供一個顯式的終止資源的方法。比如 java.io.FileInputStream 的close 方法。當使用完這個FileInputStream對象時,顯式調用close() 來回收資源。

我們首先看看 FileInputStream 是怎麼玩的:

public void close() throws IOException {

    synchronized (closeLock) {
        if (closed) {
            return;
        }
        closed = true;
    }

    if (channel != null) {
        fd.decrementAndGetUseCount();
        channel.close();
    }

    int useCount = fd.decrementAndGetUseCount();
    if ((useCount <= 0) || !isRunningFinalize()) {
        close0();
    }
}

要求此類的使用者不再使用此類的時候調用 close 終止方法,進行釋放資源,並且在類中添加一個 closed 來標記資源是否已經釋放,如果已經被釋放了,那此類中的方法如果再被調用的話就拋出異常。

抽象形式如下:

class MyObject{
    private boolean isClosed = false;
    //終止方法
    public void close(){
        //資源釋放操作...
        isClosed = true;
    }
}
public static void main(String... args) {
    MyObject object = new MyObject();
    try{
        //在這裏使用調用方法...
    }  finally {
        //在這裏關閉
        object.close();
    }
}

終結方法的用途

再繼續往下看,就會看到 FileInputStream 還是有覆蓋 finalize方法的。

protected void finalize() throws IOException {
    if ((fd != null) &&  (fd != FileDescriptor.in)) {
        runningFinalize.set(Boolean.TRUE);
         try {
             close();
         } finally {
             runningFinalize.set(Boolean.FALSE);
         }
    }
}

清楚的看到方法裏面進行調用了 close 方法,這是爲了當對象持有者忘記調用前面段落中建議的顯式終結方法 :close()的情況下,使用inalize 方法充當“安全網”這是終結方法是用途之一。

抽象形式如下:

class MyObject{
    private boolean isClosed = false;
    //終止方法
    public void close(){
        //資源釋放操作...
        isClosed = true;
    }

    //安全網
    protected void finalize() throws Throwable {
        try{
            close();
        }  finally  {
            super.finalize();
        }
    }
}

終結方法還有一個好處,把終結方法放在一個匿名的類,該匿名類的唯一用途就是終結它的外圍實例。
外圍實例持有對終結方法守衛者的唯一實例,這意味着當外圍實例是不可達時,這個終結方法守衛者也是不可達的了,垃圾回收器回收外圍實例的同時也會回收終結方法守衛者的實例,而終結方法守衛者的finalize方法就把外圍實例的資源釋放掉,就好像是終結方法是外圍實例的一個方法一樣。

看看 java.util.Timer 的終結方法守衛者:

public void cancel() {
    synchronized(queue) {
        thread.newTasksMayBeScheduled = false;
        queue.clear();
        queue.notify();  // In case queue was already empty.
    }
}

private final Object threadReaper = new Object() {
    protected void finalize() throws Throwable {
        synchronized(queue) {
            thread.newTasksMayBeScheduled = false;
            queue.notify(); // In case queue is empty.
        }
    }
};

cancel 方法是 Timer 提供的顯式終止方法,threadReaper 是一個私有變量,保證除了實例本身外沒有對它的引用,它覆蓋 finalize 方法,實現與 cancel 方法一樣的功能。

發佈了45 篇原創文章 · 獲贊 58 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章