終結方法(finalizer)通常是不可預測的,也是危險的,一般情況下是不必要的。使用終結方法會導緻行為不穩定,降低性能,以及可移植性問題。但終結方法也有其可用的地方,詳見下文。

終結方法的弊端
在java中,終結方法一般會結合 try-finally 塊來一起使用,在 finally 子句内部調用終結方法。
不要被System.gc 和 System.runFinalization 這兩個方法所誘惑,它們确實增加了終結方法被執行的機會,但是它們并不保證終結方法一定被執行。唯一聲稱保證終結方法被執行的方法是 System.runFinalizersOnExit,已經它臭名昭著的孿生兄弟 Runtime.runFinalizersOnExit。這兩個方法都有緻命缺陷,已經被廢棄了。
另一個概念是終止方法,典型顯式的終止方法例子是:InputStream、OutputStream 和 java.sql.Connection的close 方法,還有 java.util.Timer 的 cancel 方法。另外,java.awt包括了Graphics.dispose 和 Window.dispose,以及Image.flush,這些方法由于性能不好而不為關注。
終結方法缺點一:終結方法在于不能保證會被及時的執行。從一個對象變得不可達開始,到它的終結方法被執行,所花費時間是任意長的。這意味着,注重時間(time-critical)的任務不應該由終結方法來完成。
例如,使用終結方法來關閉已經打開的檔案,這是嚴重錯誤的,因為打開檔案的描述符是一種很有限的資源。由于JVM會延遲執行終結方法,是以大量的檔案會保留在打開狀态,當一個程式不能打開檔案的時候回導緻運作失敗。
- 垃圾回收算法的主要功能是:及時的執行終結方法,而這種算法在不同的JVM有不同的實作。
- 終結方法線程的優先級比其他應用程式的的其他線程的要低得多。Java 語言規範并不保證哪個線程将會執行終結方法,甚至根本不能保證它們會被執行。是以,除了不使用終結方法之外,并沒有很輕便的步伐能夠避免這樣的問題。
- 不應該依賴終結方法來更新重要的持久狀态。例如,終結方法來釋放共享資源(資料庫)的共享鎖,很容易讓整個分布式系統垮掉。
終結方法缺點二:如果未被捕獲的異常在終結過程中被抛出,那麼這種異常可以被忽略,并且該對象的終結過程也會被終結。未被捕獲的異常會使對象處于破壞的狀态(a corrupt state),如果另一個線程企圖使用該對象,則可能發生任何不确定的行為。
正常情況未捕獲的異常會使線程終止并列印堆棧軌迹,但如果異常發生在終結方法中,甚至不會列印警告!!
終結方法缺點三:使用了終結方法,會導緻嚴重的性能損失。例如,在某個機器上建立并銷毀一個簡單對象時間約為5.6ns,增加一個終結方法則會增加到2400ns。

終結方法的好處一
終結方法第一種合法用途是:當對象所有者忘記調用前面建議的顯式終止方法時,終結方法可以充當“安全網”(safety net)。
雖然這樣做不能保證終結方法會被及時執行,但在用戶端無法通過顯式調用終止方法來正常結束操作的情況下,遲一點釋放關鍵資源總永不釋放要好(如果終結方法發現資源仍未被終止,應該在日志中記錄一條警告)。
顯式終止方法的執行個體(四個類:FileInputStream、FileOutputStream 、Connection 和 Timer)都具有終結方法,當終止方法不起作用,這些終結方法便當了安全網。

終結方法的好處二
終結方法的第二種合理用途與對象的本地對等體(native peer)有關。本地對等體是一個本地對象(native object),普通對象通過本地方法委托給一個本地對象,因為本地對等體不是一個普通對象,是以垃圾回收期并不知道它。是以,在本地對等體并不擁有關鍵資源時,終結方法正是執行這項任務的最合适工具。
- 如果本地對等體擁有必須被及時終止的資源,那麼該類就應該具有一個顯式的終止方法。,這個終止方法就是完成必要的工作并釋放關鍵資源。終止方法可以是本地方法或者它調用本地方法。
- “終結方法鍊”(finalizer)同樣不會被自動執行,如果類有定義終結方法,并且子類覆寫了該終結方法,那麼子類的終結方法就得手工調用父類的終結方法:以確定即使子類的終結方法過程抛出異常,父類的終結方法也會得以執行(這也是規避常見的代碼攻擊)。
// Manual finalizer chaining @Override protected void finalize() throws Throwable { try { // Finalize subclass state } finally { super.finalize(); } }
- 另一種方式是使用終結方法守衛者(finalizer guardian)。我們不将終結方法封裝在一個要求終結處理的類中,二是放在一個匿名類裡,該匿名類唯一用途是終結它的外圍執行個體(enclosing instance),該匿名類的單個執行個體就被稱為終結方法守衛者。外部執行個體在它的私有執行個體域中儲存着一個對其終結方法守衛者的唯一引用,是以兩者可以同時啟動和終結過程。
// Finalizer Guardian idiom public class Foo { // Sole purpose of this object is to finalize outer Foo object private final Object finalizerGuardian = new Object(){ @Override protected void finalize() throws Throwable { // Finalize outer Foo object } }; // Remainder omitted }
- 注意,共有類Foo并沒有終結方法(除了從Object繼承的finalize()之外),是以子類的終結方法是否調用super.finalize 并不重要。對于每個帶有終結方法的非 final 公有類,都應該考慮這個方法。

總結
總而言之,除非是作為安全網,或者是為了終止非關鍵的本地資源,否則請不要使用終結方法。
- 在很少見的情況下,既然使用了終結方法,就要記住使用super.finalize。
- 如果用作安全網,要記得記錄終結方法的非法用法。
- 如果需要将終結方法與公有的非final類關聯起來,請考慮使用終結方法守衛者以確定:即使子類的終結方法沒有調用super.finalize,該終結方法也會被執行。
—END—
掃描二維碼
擷取技術幹貨