1.終結方法finalizer通常是不可預測的,也是很危險的.一般情況下是不必要的。使用終結方法會導致行為不穩定,降低性能,以及可移植性問題。根據經驗,應避免使用終結方法。
2.C++的程序員被告知不要把終結方法當做是C++析構器destuctors的對應物。在C++中,析構器是回收一個對象所占用資源的常規方法,是構造器所必須的對應物。在Java中,當一個對象變得不可達到的時候,垃圾回收器會回收與該對象相關聯的存儲空間,并不需要程序員做專門的工作,C++的析構器也可以用來回收其他的非內存資源。而在Java中,一般用try-finally塊來完成類似的工作。
3.終結方法的缺點在于不能保證會及時的執行。從一個對象變得不可到達開始,到它的終結方法被執行,所花費的這段時間是任意長的。即注重時間time-critical的任務不應該由終結方法來執行。如用終結方法來關閉已經打開的文件,這是嚴重錯誤,因為打開文件的描述符是一種很有限的資源。由于JVM延遲執行終結方法,所以大量的文件會保留在打開狀態,當一個程序不能再打開文件的時候,它可能會運行失敗。
4.及時的執行終結方法時垃圾回收算法的一個主要功能,這種算法在不同的JVM實現中大相徑庭。如果程序依賴終結方法被執行的時間點,那么這個程序的行為在不同的JVM中運行的表現可能會截然不同.如一個程序在你測試用的JVM平臺運行的很好,而在你的最重要的顧客的JVM平臺上卻根本無法運行, 這完全有可能。
5.延遲終結過程并不只是一個理論問題。在很少見的情況下,為類提供終結方法,可能會隨意的延遲期實例的回收過程。如可能在調試一個長期運行的GUI應用程序的時候,該應用程序總是莫名的出現OutOfMemoryError錯誤而死掉。分析表明,該應用程序死掉的時候,其終結方法隊列中有數千個圖形對象正在等待被終結和回收。遺憾的時候,終結方法線程的優先級比該應用程序的其他線程的要低很多,所以圖形對象的終結速度達不到他們進入隊列的速度。Java語言規范并不保證哪個線程將會執行終結方法,所以除了不使用終結方法之外,并沒有很輕便的方法能夠避免這樣的問題。
6.Java語言規范不僅不能保證終結方法會被及時的執行,而且根本就不保證他們會執行。當一個程序終止的時候,某些已經無法訪問的對象上的終結方法卻根本沒有被執行,這是完全可能的。結論是:不應該依賴終結方法來更新重要的持久狀態。如依賴終結方法來釋放共享資源如數據庫上的永久鎖,很容易讓這個分布式系統垮掉。
7.不要被System.gc和System.runFinalization這兩個方法所誘惑,他們確實增加了終結方法被執行的機會,但是他們并不保證終結方法一定會被執行。唯一聲稱保證終結方法被執行的方法是System.runFinalizersOnExit以及其臭名昭著的孿生兄弟Runtime.runFinalizersOnExit,這兩個方法都有致命的缺陷,已經被廢棄了。
注:System#runFinalizersOnExit|Runtime#runFinalizersOnExit:該方法具有固有的不安全性。它可能對正在使用的對象調用終結方法,而其他線程同時正在操作這些對象,從而導致不正確的行為或死鎖。
8.當你并不確定是否應該避免使用終結方法的時候,這還有一種需要考慮的情況:如果未被捕獲的異常在終結過程中被拋出來了,那么這種異常可以被忽略,并且該對象的終結過程也會被終止。未被捕獲的異常會使對象處于破壞的狀態a corrupt state,如果另一個線程企圖使用這種被破壞的對象,則可能發生任何不確定的行為。 正常情況下,未被捕獲的異常會使線程終止,并打印出棧軌跡stack trace。但是如果異常發生在終結方法之中,則不會如此,甚至連警告都不會打印出來。
9.還有一點,使用終結方法有一個非常嚴重的Severe性能損失。某測試數據:創建和銷毀一個簡單對象的時間大約為5.6ns。增加一個終結方法使時間增加到了2400ns。換句話說,用終結方法創建和銷毀對象慢了大約430倍。
10.如果類的對象中封裝的資源如文件或者線程確實需要終止,應該如何做而不編寫終結方法呢。只需提供一個顯示的終止方法,并要求該類的客戶端在每個實例不再有用的時候調用這個方法。有一個細節是該實例必須記錄下自己是否已經被終止了;顯示的終止方法必須在一個私有域中記錄下“該對象已經不再有效”。如果這些方法時在對象已經終止之后被調用,其他的方法就必須檢查這個于,并拋出IllegalStateException。
顯示終止方法的典型例子是InputStream,OutputStream,java.sql.Connection上的close方法。另一個例子是java.util.Timer上的cancel方法,它執行必要的狀態改變,是的與Timer實例相關聯的該線程溫和的終止自己。java.awt中的例子還包括Graphics.dispose和Window.dispose這些方法通常由于性能不好而不被人們關注。一個相關的方法時Image.flush,它會釋放所有域Image實例相關資源,但是該實例仍然處于可用的狀態,如果有必要的話,會重新分配資源.
注:如1.FileOutputStream#private volatile boolean closed = false;
2.java.io.Closeable | java.lang.AutoCloseable[since 1.7]|
{@link ExplicitClose}
11.終結方法的好處:
1.當對象的所有者忘記調用之前建議的顯示終止方法時,終結方法可以充當安全網,safety net.雖然這種做并不能保證終結方法會被及時的調用,但是在客戶端無法通過調用顯示的終止方法來正確結束操作的情況下(希望這種情形盡可能的少發生),遲一點釋放關鍵資源總比永遠不釋放要好。但是如果終結方法發現資源還未被終止,則應該在日志中記錄一條警告。因為這表示客戶端代碼中的一個bug,應該得到修復。如果正考慮編寫這樣的安全網終結方法,就要考慮清楚這種額外的保護是否值得付出額外的代價。
2.顯示終止方法模式的實例中所示的四個類FileInputStream,FileOutputStream,Timer和Connection,都具有終結方法。當他們的終止方法未能被調用的情況下,這些終結方法充當了安全網。
注:FileInputStream#filnalize:
/**
* Ensures that the <code>close</code> method of this file input stream is
* called when there are no more references to it.
*
* @exception IOException if an I/O error occurs.
* @see java.io.FileInputStream#close()
*/
protected void finalize() throws IOException {
if ((fd != null) && (fd != FileDescriptor.in)) {
/*
* Finalizer should not release the FileDescriptor if another
* stream is still using it. If the user directly invokes
* close() then the FileDescriptor is also released.
*/
runningFinalize.set(Boolean.TRUE);
try {
close();
} finally {
runningFinalize.set(Boolean.FALSE);
}
}
}
3.終結方法的第二種合理用于與對象的本地對等體native peer有關。本地對等體是一個本地對象native object,普通對象通過本地方法native method委托給一個本地對象。因為本地對等體不是一個普通對象,所以垃圾回收器不會知道它。當它的Java對等體被回收的時候,它不會被回收。在本地對等體并不擁有關鍵資源的前提下(注是否因為關鍵資源可被本地其他進程使用),終結方法正是執行任務的最合適的工具。如果本地對等體用于必須被及時終止的資源, 那么該類就應該具有一個顯示的終止方法,如前所述。終止方法應該完成所有必要的工作以便釋放關鍵的資源。終止方法可以使本地方法或者也可以調用本地方法。
12.注意終結方法鏈finalizer chaining并不會自動執行。如果類不是Object有終結方法,并且子類覆蓋了終結方法,子類的終結方法就必須手動調用超類的終結方法。你應該在一個try快中終結子類,并且在相應的finally塊中調用超類的終結方法。這樣可以保證,即使子類的終結過程拋出異常,超類的終結方法也會得到執行。反之亦然。注:{@link FinalizerChaining}
13.如果子類實現這覆蓋了超類的終結方法,但是忘了手工調用超類的終結方法或者有意選擇不調用超類的終結方法,那么超類的終結方法將永遠不會得到調用。要防范這樣的粗心大意或者而已的子類是有可能的,代價就是為每個將被終結的對象創建一個附加的對象,不是把終結方法放在要求終結處理的類中,而是把終結方法放在一個匿名的類中,該匿名類的唯一用途是終結它的外圍實例enclosing instance.該匿名類的單個實例被稱為終結方法守衛者finalizer guardian.外圍類的每個實例都會創建這樣一個守衛者,外圍實例在它的私有實例域中保存一個對其終結方法守衛者的唯一引用,因為終結方法守衛者與外圍實例可以同時啟動終結過程。當守衛者被終結的時候, 它執行外圍實例所期望的終結行為,就好像它的終結方法是外圍對象上的一個方法一樣。
注:{@link FinalizerGuardian}-->在終結方法守衛者以為是實例變量?,所以必然會被銷毀?,進而其終結的時候必然會調用外圍類的終結發方法。
14.總結:除非是作為安全網或者是為了終止非關鍵的本地資源,否則請不要使用終結方法。在這些很少見的情況下,既然使用了終結方法就要記住調用super.finalize.如果用終結方法作為安全網,要記得記錄終結方法的非法用法。最好,如果需要把終結方法與公有的非final(注:final的無子類)的類關聯起來,請考慮使用終結方法守衛者,以確保即使子類的終結方法未能調用super.finalize,該終結方法也會被執行。
部分源碼:





























































































{









