在說明finalize()的用法之前要樹立有關於java垃圾回收器幾個觀點:
- "對象可以不被垃圾回收" : java的垃圾回收遵循一個特點, 就是能不回收就不會回收.只要程序的內存沒有達到即將用完的地步, 對象占用的空間就不會被釋放.因為如果程序正常結束了,而且垃圾回收器沒有釋放申請的內存, 那么隨着程序的正常退出, 申請的內存會自動交還給操作系統; 而且垃圾回收本身就需要付出代價, 是有一定開銷的, 如果不使用,就不會存在這一部分的開銷.
- 垃圾回收只能回收內存, 而且只能回收內存中由java創建對象方式(堆)創建的對象所占用的那一部分內存, 無法回收其他資源, 比如文件操作的句柄, 數據庫的連接等等.
- 垃圾回收不是C++中的析構. 兩者不是對應關系, 因為第一點就指出了垃圾回收的發生是不確定的, 而C++中析構函數是由程序員控制(delete) 或者離開器作用域時自動調用發生, 是在確定的時間對對象進行銷毀並釋放其所占用的內存.
- 調用垃圾回收器(GC)不一定保證垃圾回收器的運行
finalize()的功能 : 一旦垃圾回收器准備釋放對象所占的內存空間, 如果對象覆蓋了finalize()並且函數體內不能是空的, 就會首先調用對象的finalize(), 然后在下一次垃圾回收動作發生的時候真正收回對象所占的空間.
finalize()有一個特點就是: JVM始終只調用一次. 無論這個對象被垃圾回收器標記為什么狀態, finalize()始終只調用一次. 但是程序員在代碼中主動調用的不記錄在這之內.
如果需要釋放對象的父類所占的資源, 那么就必須自己手動構造finalize調用鏈
protected void finalize() throws Throwable { super.finalize(); }
finalize()主要使用的方面:
- 根據垃圾回收器的第2點可知, java垃圾回收器只能回收創建在堆中的java對象, 而對於不是這種方式創建的對象則沒有方法處理, 這就需要使用finalize()對這部分對象所占的資源進行釋放. 使用到這一點的就是JNI本地對象, 通過JNI來調用本地方法創建的對象只能通過finalize()保證使用之后進行銷毀,釋放內存
- 充當保證使用之后釋放資源的最后一道屏障, 比如使用數據庫連接之后未斷開,並且由於程序員的個人原因忘記了釋放連接, 這時就只能依靠finalize()函數來釋放資源.
- 《thinking in java》中所講到的“終結條件”驗證, 通過finalize()方法來試圖找出程序的漏洞
盡管finalize()可以主動調用, 但是最好不要主動調用, 因為在代碼中主動調用之后, 如果JVM再次調用, 由於之前的調用已經釋放過資源了,所以二次釋放資源就有可能出現導致出現空指針等異常, 而恰好這些異常是沒有被捕獲的, 那么就造成對象處於被破壞的狀態, 導致該對象所占用的某一部分資源無法被回收而浪費.
盡量避免使用finalize():
- finalize()不一定會被調用, 因為java的垃圾回收器的特性就決定了它不一定會被調用
- 就算finalize()函數被調用, 它被調用的時間充滿了不確定性, 因為程序中其他線程的優先級遠遠高於執行finalize()函數線程的優先級。也許等到finalize()被調用, 數據庫的連接池或者文件句柄早就耗盡了.
- 如果一種未被捕獲的異常在使用finalize方法時被拋出,這個異常不會被捕獲,finalize方法的終結過程也會終止,造成對象出於破壞的狀態。被破壞的對象又很可能導致部分資源無法被回收, 造成浪費.
- finalize()和垃圾回收器的運行本身就要耗費資源, 也許會導致程序的暫時停止.
finalize執行的生命周期:
- finalize流程:當對象變成(GC Roots)不可達時,GC會判斷該對象是否覆蓋了finalize方法,若未覆蓋,則直接將其回收。否則,若對象未執行過finalize方法,將其放入F-Queue隊列,由一低優先級線程執行該隊列中對象的finalize方法。執行finalize方法完畢后,GC會再次判斷該對象是否可達,若不可達,則進行回收,否則,對象“復活”。
- 對象可由兩種狀態組成,涉及到兩類狀態空間,一是終結狀態空間 F = {unfinalized, finalizable, finalized};二是可達狀態空間 R = {reachable, finalizer-reachable, unreachable}。
- unfinalized : GC未調用對象的finalize(), 也不准備調用對象的finalize().
- finalizable: 表示GC可調用對象的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方法
對象重生的代碼1:
class C { static A a; } class A { B b; public A(B b) { this.b = b; } @Override public void finalize() { System.out.println("A finalize"); C.a = this; } } class B { String name; int age; public B(String name, int age) { this.name = name; this.age = age; } @Override public void finalize() { System.out.println("B finalize"); } @Override public String toString() { return name + " is " + age; } } public class Main { public static void main(String[] args) throws Exception { A a = new A(new B("allen", 20)); a = null; System.gc(); Thread.sleep(5000); System.out.println(C.a.b); } }
我的理解:為方便起見, 把a,b兩個變量所指的內存空間就叫做a和b
- A a = new A(new B("allen" , 20)) ; //此時a和b都是reachable, unfinalized狀態
- a = null ;
- 這之后, a和b的狀態會在某一個時刻變成unreachable, unfinalized(但是b變成了unreachable還是f-reachable我不是很確定, 如果大家知道,歡迎補充^_^)
- 或者a和b直接變成f-reachable, unfianlized.
- 然后在某個時刻,GC檢測到a和b處於unfinalized狀態, 就將他們添加到F-queue,並將狀態改為f-reachable finalizable.
- 之后分兩種情況:
- 第一: GC從F-queue中首先取出a, 並被某個線程執行了finalize(), 也就相當於被某個活動的線程持有, a狀態變成了reachable, finalized. 此時由於a被c對象所引用,所以之后不會變成unreachable finalized而被銷毀(重生) 與此同時, b由於一直被a所引用, 所以b的狀態變成了reachable, finalizable. 然后在某個時刻被從F-queue取出, 變成reachable, finalized狀態
- 第二: GC從F-queue中首先取出b,並被某個線程執行了finalize(), 狀態變成reachable finalized. 然后a也類似, 變成reachable finalized狀態, 並被c引用, 重生
對象重生的代碼2:
public class GC { public static GC SAVE_HOOK = null; public static void main(String[] args) throws InterruptedException, Throwable { SAVE_HOOK = new GC(); SAVE_HOOK = null; System.gc(); Thread.sleep(500); if (null != SAVE_HOOK) //此時對象應該處於(reachable, finalized)狀態 { System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } SAVE_HOOK = null; System.gc(); Thread.sleep(500); if (null != SAVE_HOOK) { System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("execute method finalize()"); SAVE_HOOK = this; } }
//資料來自: http://blog.csdn.net/rsljdkt/article/details/12242007# ; http://blog.sina.com.cn/s/blog_66a6172c01018jda.html ; http://zhang-xzhi-xjtu.iteye.com/blog/484934
//《thingking in java》筆記。 如果有什么不對的地方, 歡迎指正^_^