1. 程式人生 > >Java中的finalize方法與gc之間的關係

Java中的finalize方法與gc之間的關係

一、可達性演算法

  要知道物件什麼時候死亡,我們需要先知道JVM的GC是如何判斷物件是可以回收的。JAVA是通過可達性演算法來來判斷物件是否存活的。這個演算法的基本思路就是通過一系列的稱為“GC Roots”的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots 沒有任何引用鏈相連時,則證明此物件是不可用的。

  在JAVA語言中,可以作為GC Roots的物件包括下面幾種:

    * 虛擬機器棧(棧幀中的本地變量表)中引用的物件。

    * 方法區中類靜態屬性引用的物件

    * 方法區中常量引用的物件

    * 本地方法棧中JNI(即一般說的Native方法)引用的物件

  引用計數演算法:這也是一個判斷物件是否存活的演算法,相對於可達性演算法較為簡單,判斷效率也很高,但是沒有在JAVA中使用。給物件中新增一個引用計數器,每當一個地方引用它時,計數器值就加1;當引用失效時,計數器就減1;任何時刻計數器為0的物件就是不可能再被使用的。

二、引用

  JDK1.2之後,JAVA將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。

    * 強引用:類似Object obj = new Object()這類引用,只要引用還在,垃圾收集器就不會回收掉被引用的物件。

    * 軟引用:系統發生記憶體溢位異常之前,會把軟引用的物件列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會丟擲記憶體溢位異常。

    * 弱引用:只要進行垃圾回收,就會回收掉引用關聯的物件。

    * 虛引用:不會對生存時間構成影響,唯一的目的是物件被回收時收到一個系統通知。

三、物件的復活及finalize()方法

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

  如果這個物件被判定為有必要執行finalize()方法,那麼這個物件將會放置在一個叫做F-Queue的佇列之中,並在稍後由一個由虛擬機器自動建立的、低優先順序的Finalizer執行緒去執行它。這裡所謂的“執行”是指虛擬機器會觸發這個方法,但並不承諾會等待它執行結束,這樣做的原因是如果一個物件在finalize()方法中執行緩慢,或者發生了死迴圈(更極端的情況),將很可能會導致F-Queue佇列中其他物件永久處於等待,甚至導致整個記憶體回收系統崩潰。

  finalize()方法是物件逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的物件進行第二次小規模的標記,如果物件要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個物件建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變數或者物件的成員變數,那麼在第二次標記時它將被移除出“即將回收”的集合;如果物件這時候還沒有逃脫,那麼基本上它就真的被回收了。

  總結:finalize()並不是必須要執行的,它只能執行一次或者0次。如果在finalize中建立物件關聯,則當前物件可以復活一次。

3. finalize的執行過程(生命週期)

(1) 首先,大致描述一下finalize流程:當物件變成(GC Roots)不可達時,GC會判斷該物件是否覆蓋了finalize方法,若未覆蓋,則直接將其回收。否則,若物件未執行過finalize方法,將其放入F-Queue佇列,由一低優先順序執行緒執行該佇列中物件的finalize方法。執行finalize方法完畢後,GC會再次判斷該物件是否可達,若不可達,則進行回收,否則,物件“復活”。

(2) 具體的finalize流程:

物件可由兩種狀態,涉及到兩類狀態空間,一是終結狀態空間 F = {unfinalized, finalizable, finalized};二是可達狀態空間 R = {reachable, finalizer-reachable, unreachable}。各狀態含義如下:

unfinalized: 新建物件會先進入此狀態,GC並未準備執行其finalize方法,因為該物件是可達的

finalizable: 表示GC可對該物件執行finalize方法,GC已檢測到該物件不可達。正如前面所述,GC通過F-Queue佇列和一專用執行緒完成finalize的執行

finalized: 表示GC已經對該物件執行過finalize方法

reachable: 表示GC Roots引用可達

finalizer-reachable(f-reachable):表示不是reachable,但可通過某個finalizable物件可達

unreachable:物件不可通過上面兩種途徑可達

狀態變遷圖:

變遷說明:

新建物件首先處於[reachable, unfinalized]狀態(A)

隨著程式的執行,一些引用關係會消失,導致狀態變遷,從reachable狀態變遷到f-reachable(B, C, D)或unreachable(E, F)狀態

若JVM檢測到處於unfinalized狀態的物件變成f-reachable或unreachable,JVM會將其標記為finalizable狀態(G,H)。若物件原處於[unreachable, unfinalized]狀態,則同時將其標記為f-reachable(H)。

在某個時刻,JVM取出某個finalizable物件,將其標記為finalized並在某個執行緒中執行其finalize方法。由於是在活動執行緒中引用了該物件,該物件將變遷到(reachable, finalized)狀態(K或J)。該動作將影響某些其他物件從f-reachable狀態重新回到reachable狀態(L, M, N)

處於finalizable狀態的物件不能同時是unreahable的,由第4點可知,將物件finalizable物件標記為finalized時會由某個執行緒執行該物件的finalize方法,致使其變成reachable。這也是圖中只有八個狀態點的原因

程式設計師手動呼叫finalize方法並不會影響到上述內部標記的變化,因此JVM只會至多呼叫finalize一次,即使該物件“復活”也是如此。程式設計師手動呼叫多少次不影響JVM的行為

若JVM檢測到finalized狀態的物件變成unreachable,回收其記憶體(I)

若物件並未覆蓋finalize方法,JVM會進行優化,直接回收物件(O)

注:System.runFinalizersOnExit()等方法可以使物件即使處於reachable狀態,JVM仍對其執行finalize方法