深刻理解java的finalize

基本預備相關知識

1 java的GC只負責內存相關的清理,全部其它資源的清理必須由程序員手工完成。要否則會引發資源泄露,有可能致使程序崩潰。

2 調用GC並不保證GC實際執行。

3 finalize拋出的未捕獲異常只會致使該對象的finalize執行退出。

4 用戶能夠本身調用對象的finalize方法,可是這種調用是正常的方法調用,和對象的銷燬過程無關。

5 JVM保證在一個對象所佔用的內存被回收以前,若是它實現了finalize方法,則該方法必定會被調用。Object的默認finalize什麼都不作,爲了效率,GC能夠認爲一個什麼都不作的finalize不存在。

6 對象的finalize調用鏈和clone調用鏈同樣,必須手工構造。

Java代碼   收藏代碼
  1. protected void finalize() throws Throwable {  
  2.     super.finalize();  
  3. }  



對象的銷燬過程

在對象的銷燬過程當中,按照對象的finalize的執行狀況,能夠分爲如下幾種,系統會記錄對象的對應狀態:
unfinalized 沒有執行finalize,系統也不許備執行。
finalizable 能夠執行finalize了,系統會在隨後的某個時間執行finalize。
finalized 該對象的finalize已經被執行了。

GC怎麼來保持對finalizable的對象的追蹤呢。GC有一個Queue,叫作F-Queue,全部對象在變爲finalizable的時候會加入到該Queue,而後等待GC執行它的finalize方法。

這時咱們引入了對對象的另一種記錄分類,系統能夠檢查到一個對象屬於哪種。
reachable 從活動的對象引用鏈能夠到達的對象。包括全部線程當前棧的局部變量,全部的靜態變量等等。
finalizer-reachable 除了reachable外,從F-Queue能夠經過引用到達的對象。
unreachable 其它的對象。

來看看對象的狀態轉換圖。


好大,好暈,慢慢看。

1 首先,全部的對象都是從Reachable+Unfinalized走向死亡之路的。

2 當從當前活動集到對象不可達時,對象能夠從Reachable狀態變到F-Reachable或者Unreachable狀態。

3 當對象爲非Reachable+Unfinalized時,GC會把它移入F-Queue,狀態變爲F-Reachable+Finalizable。

4 好了,關鍵的來了,任什麼時候候,GC均可以從F-Queue中拿到一個Finalizable的對象,標記它爲Finalized,而後執行它的 finalize方法,因爲該對象在這個線程中又可達了,因而該對象變成Reachable了(而且Finalized)。而finalize方法執行 時,又有可能把其它的F-Reachable的對象變爲一個Reachable的,這個叫作對象再生。

5 當一個對象在Unreachable+Unfinalized時,若是該對象使用的是默認的Object的finalize,或者雖然重寫了,可是新的實 現什麼也不幹。爲了性能,GC能夠把該對象之間變到Reclaimed狀態直接銷燬,而不用加入到F-Queue等待GC作進一步處理。

6 從狀態圖看出,無論怎麼折騰,任意一個對象的finalize只至多執行一次,一旦對象變爲Finalized,就怎麼也不會在回到F-Queue去了。固然沒有機會再執行finalize了。

7 當對象處於Unreachable+Finalized時,該對象離真正的死亡不遠了。GC能夠安全的回收該對象的內存了。進入Reclaimed。


對象重生的例子
Java代碼   收藏代碼
  1. class C {  
  2.     static A a;  
  3. }  
  4.   
  5. class A {  
  6.     B b;  
  7.   
  8.     public A(B b) {  
  9.         this.b = b;  
  10.     }  
  11.   
  12.     @Override  
  13.     public void finalize() {  
  14.         System.out.println("A finalize");  
  15.         C.a = this;  
  16.     }  
  17. }  
  18.   
  19. class B {  
  20.     String name;  
  21.     int age;  
  22.   
  23.     public B(String name, int age) {  
  24.         this.name = name;  
  25.         this.age = age;  
  26.     }  
  27.   
  28.     @Override  
  29.     public void finalize() {  
  30.         System.out.println("B finalize");  
  31.     }  
  32.   
  33.     @Override  
  34.     public String toString() {  
  35.         return name + " is " + age;  
  36.     }  
  37. }  
  38.   
  39. public class Main {  
  40.     public static void main(String[] args) throws Exception {  
  41.         A a = new A(new B("allen"20));  
  42.         a = null;  
  43.   
  44.         System.gc();  
  45.         Thread.sleep(5000);  
  46.         System.out.println(C.a.b);  
  47.     }  
  48. }  


期待輸出
Java代碼   收藏代碼
  1. A finalize  
  2. B finalize  
  3. allen is 20  

可是有可能失敗,源於GC的不肯定性以及時序問題,多跑幾回應該能夠有成功的。詳細解釋見文末的參考文檔。

對象的finalize的執行順序

全部finalizable的對象的finalize的執行是不肯定的,既不肯定由哪一個線程執行,也不肯定執行的順序。
考慮如下狀況就明白爲何了,實例a,b,c是一組相互循環引用的finalizable對象。

什麼時候及如何使用finalize

從以上的分析得出,如下結論。
1 最重要的,儘可能不要用finalize,太複雜了,仍是讓系統照管比較好。能夠定義其它的方法來釋放非內存資源。
2 若是用,儘可能簡單。
3 若是用,避免對象再生,這個是本身給本身找麻煩。
4 能夠用來保護非內存資源被釋放。即便咱們定義了其它的方法來釋放非內存資源,可是其它人未必會調用該方法來釋放。在finalize裏面能夠檢查一下,若是沒有釋放就釋放好了,晚釋放總比不釋放好。
5 即便對象的finalize已經運行了,不能保證該對象被銷燬。要實現一些保證對象完全被銷燬時的動做,只能依賴於java.lang.ref裏面的類和GC交互了。

參考

關於引用類型,GC,finalize的相互交互能夠參考 ReferenceQueue GC finalize Reference 測試及相關問題
相關文章
相關標籤/搜索