Effective Java -- 避免使用終結方法

本文是 《Effective Java Second Edition》第7條的讀書筆記。

以下使用的JVM參數爲-Xmx5m -Xmn5m


首先我們要弄清楚什麼是終結方法(finalizer)?

終結方法是我們Object 類中的一個通用方法,其定義如下

    @Deprecated(since="9")
    protected void finalize() throws Throwable { }

上面的註解表示其在JDK1.9 中是已經不再支持使用的了。

對於學習過C++ 的都應該會知道有一個與構造函數相對應的函數我們稱之爲析構函數。而我們的終結方法就是類似於C++ 中的這個析構函數,只要是用來回收一些不可達的對象的。

但是這個終結方法有一個致命的缺點就是:

當一個對象變得不可達的時候,該對象的終結方法的執行時間是任意的,在大多數的情況下可能是不會執行的。

我們在這裏來測試一下對象的終結方法時候會被執行。
如下代碼

package com.blog.effective.note7;

/**
 * 自定義一個重寫終結方法的方法.
 *
 * @author 張俊強~.
 * @date 2017/11/27-22:17.
 */
public class MyFileOutputStream  {

    public MyFileOutputStream() {
        super();
    }

    @Deprecated(since="9")
    protected void finalize() throws Throwable {
        System.out.println("調用終結方法");
        System.exit(0);             //調用終結方法我們強制退出
        super.finalize();
    }
}

在客戶端我們的測試代碼如下

    public static void main(String[] args) {
        int i = 0;                  //標記while循環執行的次數
        while (true) {
            MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
            System.out.println("M:" + (i++));
        }
    }

測試後的結果爲:在while循序執行了6769次後,開始調用了對象的終結方法。

[注]. 如果不使用while()循環(只執行一次),在我測試很多次之後還是沒有發現有調用終結方法的現象。


有兩個方法方法會加快對象的終結方法的調用速度。

System.gc()System.runFinalization()

如下的客戶端代碼

    public static void main(String[] args) {
        int i = 0;                  //標記while循環執行的次數
        while (true) {
            MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
            System.gc();
            System.out.println("M:" + (i++));
        }
    }

輸入如下

M:0
M:1
調用終結方法
    public static void main(String[] args) {
        int i = 0;                  //標記while循環執行的次數
        while (true) {
            MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
            System.runFinalization();
            System.out.println("M:" + (i++));
        }
    }

輸入如下

M:2038
M:2039
M:2040
調用終結方法

可以原來需要運行六千多次纔會調用終結方法,被提前調用了。

那麼有什麼會保證相應的終結方法一定會被執行呢?
System.runFinalizersOnExit()Runtime.runFinalizersOnExit() 可以保證finalizer()方法一定執行,但是這兩個方法已經廢棄。


如果不建議使用終結方法,那麼有什麼替代做法呢?

使用try-finally結構來顯示的調用父類的終結方法

這就是我們在一些對於IO 流的操作,或者數據庫的連接的時候,會要求在finally 中顯示的調用close() 方法的原因。

我們那FileInputStream 這個類爲例,查看一下其finalizer() 方法。

    @Deprecated(since="9")
    protected void finalize() throws IOException {
        if ((fd != null) &&  (fd != FileDescriptor.in)) {
            close();
        }
    }

我們就可以知道其對應的終結方法也是調用close() 方法來釋放空間的。

使用finalizer guardian(終結方法守衛者)
爲了防止之類在重寫父的終結方法時候忘記加上super.finalize(); ,我們可以就使用這個方法。

如下代碼

public class MyFileOutputStream  {

    public MyFileOutputStream() {
        super();
    }

    private final Object finalizerGuardian =new Object(){
        @Override
        protected void finalize() throws Throwable {
            System.out.println("調用終結方法");
            super.finalize();
        }
    };
}

客戶端程序

    public static void main(String[] args) {
        int i = 0;                  //標記while循環執行的次數
        MyFileOutputStream myFileOutputStream = new MyFileOutputStream();
        myFileOutputStream=null;
        System.gc();
    }

發現我們的終結方法被調用了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章