對象最後一次自我救贖——finalize()

在根搜索算法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程:如果對象在進行根搜索後發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視爲“沒有必要執行”。

如果這個對象被判定爲有必要執行finalize()方法,那麼這個對象將會被放置在一個名爲F-Queue的隊列之中,並在稍後由一條由虛擬機自動建立的、低優先級的Finalizer線程去執行。這裏所謂的“執行”是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束。

這樣做的原因是,如果一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的情況),將很可能會導致F-Queue隊列中的其他對象永久處於等待狀態,甚至導致整個內存回收系統崩潰。

finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模的標記,如果對象要在finalize()中成功拯救自己—只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變量或對象的成員變量,那在第二次標記時它將被移除出“即將回收”的集合;如果對象這時候還沒有逃脫,那它就真的離死不遠了。從代碼清單中我們可以看到一個對象的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(); 
//因爲Finalizer方法優先級很低,暫停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(); 
//因爲Finalizer方法優先級很低,暫停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:(

從代碼清單的運行結果可以看到,SAVE_HOOK對象的finalize()方法確實被GC收集器觸發過,並且在被收集前成功逃脫了。 另外一個值得注意的地方就是,代碼中有兩段完全一樣的代碼片段,執行結果卻是一次逃脫成功,一次失敗,這是因爲任何一個對象的finalize()方法都只會被系統自動調用一次,如果對象面臨下一次回收,它的finalize()方法不會被再次執行,因此第二段代碼的自救行動失敗了。

注:本文參考自周志明的《深入理解Java虛擬機》,非常感謝大佬的書!

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