天天看點

【深入Java虛拟機JVM 09】JVM垃圾回收finalize方法--對象最有一次自我拯救

說明:文章所有内容均摘自《深入了解Java虛拟機:JVM進階特性與最佳實踐(第二版)》

【深入Java虛拟機JVM 09】JVM垃圾回收finalize方法--對象最有一次自我拯救

即使在可達性分析算法中不可達的對象,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段。

要真正宣告一個對象死亡,至少要經曆兩次标記過程:如果對象在進行可達性分析後發現沒有與GC Roots相連接配接的引用鍊,那它将會被第一次标記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆寫finalize()方法,或者finalize()方法已經被虛拟機調用過,虛拟機将這兩種情況都視為“沒有必要執行”。

如果這個對象被判定為有必要執行finalize()方法,那麼這個對象将會放置在一個叫做F-Queue的隊列之中,并在稍後由一個由虛拟機自動建立的、低優先級的Finalizer線程去執行它。這裡所謂的“執行”是指虛拟機會觸發這個方法,但并不承諾會等待它運作結束,這樣做的原因是,如果一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的情況),将很可能會導緻F-Queue隊列中其他對象永久處于等待,甚至導緻整個記憶體回收系統崩潰。finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC将對F-Queue中的對象進行第二次小規模的标記,如果對象要在finalize()中成功拯救自己——隻要重新與引用鍊

上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)指派給某個類變量或者對象的成員變量,那在第二次标記時它将被移除出“即将回收”的集合;如果對象這時候還沒有逃脫,那基本上它就真的被回收了。從代碼清單3-2中我們可以看到一個對象的finalize()被執行,但是它仍然可以存活。

/**
 * 此代碼示範了兩點:
 * 1.對象可以在被GC時自我拯救。
 * 2.這種自救的機會隻有一次,因為一個對象的finalize()方法最多隻會被系統自動調用一次
 *
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes,i am still alive:)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize mehtod executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        //對象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        //因為finalize方法優先級很低,是以暫停0.5秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no,i am dead:(");
        }
        //下面這段代碼與上面的完全相同,但是這次自救卻失敗了
        SAVE_HOOK = null;
        System.gc();
        //因為finalize方法優先級很低,是以暫停0.5秒以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no,i am dead:(");
        }
    }
}

運作結果:
finalize mehtod executed!
yes,i am still alive:)
no,i am dead:(
           

從代碼清單3-2的運作結果可以看出,SAVE_HOOK對象的finalize()方法确實被GC收集器觸發過,并且在被收集前成功逃脫了。

另外一個值得注意的地方是,代碼中有兩段完全一樣的代碼片段,執行結果卻是一次逃脫成功,一次失敗,這是因為任何一個對象的finalize()方法都隻會被系統自動調用一次,如果對象面臨下一次回收,它的finalize()方法不會被再次執行,是以第二段代碼的自救行動失敗了。

需要特别說明的是,上面關于對象死亡時finalize()方法的描述可能帶有悲情的藝術色彩,筆者并不鼓勵大家使用這種方法來拯救對象。相反,筆者建議大家盡量避免使用它,因為它不是C/C++中的析構函數,而是Java剛誕生時為了使C/C++程式員更容易接受它所做出的一個妥協。它的運作代價高昂,不确定性大,無法保證各個對象的調用順序。有些教材中描述它适合做“關閉外部資源”之類的工作,這完全是對這個方法用途的一種自我安慰。finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及時,是以筆者建議大家完全可以忘掉Java語言中有這個方法的存在。