《Effective Java 中文版 第2版》學習筆記 第7條:避免使用終結方法
在Java中,當一個對象變得不可到達時,垃圾回收器會回收與該對象相關聯的存儲空間。用try-finally塊來回收其他的非內存資源。
終結方法的缺點在於不能保證會被及時地執行。從一個對象變得不可到達開始,到它的終結方法被執行,所花費的這段時間是任意長的。這意味著註重時間的任務不應該由終結方法來完成。例如,用終結方法來關閉已經打開的文件是錯誤的,因為打開文件的描述符是一種很有限的資源。由於JVM會延遲執行終結方法,所以大量的文件會保留在打開狀態,當一個程序再不能打開文件的時候,它可能會運行失敗。
及時地執行終結方法是垃圾回收算法的一個主要功能,這種算法在不同的JVM實現中會大相徑庭。如果程序依賴於終結方法被執行的時間點,這個程序的行為在不同的JVM中運行的表現可能會截然不同。一個程序在自己測試用的JVM平臺上運行得非常好,而在客戶的JVM平臺上可能根本無法運行。
Java語言規範不僅不保證終結方法會被及時地執行,而且根本就不保證它們會被執行。當一個程序終止時,某些已經無法訪問的對象上的終結方法可能根本沒有被執行。所以,不應該依賴終結方法來更新重要的持久狀態。例如,依賴終結方法來釋放共享資源(比如數據庫)上的永久鎖,很容易讓整個分布式系統垮掉。
System.gc和System.runFinalization這兩個方法雖然增加了終結方法被執行的機會,但是不保證一定會執行終結方法。System.runFinalizersOnExit以及其孿生兄弟Runtime.runFinalizersOnExit這兩個方法雖然聲稱保證終結方法被執行,但是都有致命的缺陷,已經被廢棄了。
如果未被捕獲的異常在終結過程中被拋出來,這種異常會被忽略,並且該對象的終結過程也會終止。換言之,如果異常發生在終結方法之中,它不會使線程終止,也不會打印出棧軌跡(Stack Trace),甚至不會打印警告。
使用終結方法會有嚴重的(Severe)性能損失。也就是說,增加終結方法會使處理速度變慢。
如果類的對象中封裝的資源(例如文件或者線程)需要終止,只需提供一個顯式的終止方法,並要求該類的客戶端在每個實例不再有用時調用這個方法。其中,顯式的終止方法必須在一個私有域中記錄下“該對象已經不再有效”。如果終止方法是在對象已經終止之後被調用,其他的方法必須檢查這個域,並拋出IllegalStateException異常。顯式的終結方法通常與try-finally結構結合起來使用,確保及時終止。在finally子句內部調用顯式的終止方法,保證即使在使用對象時拋出異常,該終止方法也會執行。
終結方法有兩種合法用途。
第一種用途:當對象的所有者忘記調用顯式終止方法時,終結方法可以充當“安全網(safety net)”,遲一點釋放關鍵資源總比永遠不釋放要好。
第二種用途:本地對等體(native peer)是一個本地對象(native object),普通對象通過本地方法(native method)委托給一個本地對象。因為本地對等體不是一個普通對象,所以垃圾回收器不會知道它,當它的Java對等體被回收時,它不會被回收。在本地對等體不擁有關鍵資源的前提下,終結方法會回收它。如果本地對等體擁有必須被及時終止的資源,該類就應該具有一個顯式的終止方法。終止方法可以是本地方法,也可以調用本地方法。
“終結方法鏈(finalizer chaining)”不會被自動執行。如果類(不是Object)有終結方法,並且子類覆蓋了終結方法,子類的終結方法就必須手工調用超類的終結方法。在一個try塊中終結子類,並在相應的finally塊中調用超類的終結方法。保證即使子類的終結過程拋出異常,超類的終結方法也會得到執行。
1 @Override 2 protected void finalize() throws Throwable { 3 try { 4 ... // 終結子類狀態 5 } finally { 6 super.finalize(); // 終結父類狀態 7 } 8 }
總之,除非是作為安全網,或者是為了終止非關鍵的本地資源,否則請不要使用終結方法。
參考資料
《Effective Java 中文版 第2版》 P24-27
《Effective Java 中文版 第2版》學習筆記 第7條:避免使用終結方法