閱讀經典——《Effective Java》03
Java語言包中的Object類是所有類的祖先。該類提供了諸如equals、hashCode、toString、clone、finalize等方法。本文重點討論finalize方法,其余將在后面文章中陸續出現。
finalize方法
Java文檔對finalize方法的解釋如下。
當對象不再擁有可到達的引用時,垃圾回收器在回收該對象的空間前調用它的finalize方法。子類可在該方法中釋放占用的系統資源或執行其它清理工作。
通常,我們會用finalize方法關閉已經打開的文件,但是請注意,這是嚴重錯誤!
finalize方法的缺點在于不保證會被及時的執行,甚至根本不保證一定會執行。很多時候直到程序終止仍然有不可達對象的finalize方法沒有被執行。因此,在finalize中關閉文件很可能失敗。
另外,System.gc()
和System.runFinalization()
只是增加finalize被執行的機會。唯一聲稱保證finalize方法在程序終止前執行的是System.runFinalizersOnExit
和Runtime.runFinalizersOnExit
。但它們有致命的缺陷,已經被廢棄了。
finalize中拋出的異常會被忽略,以至于程序員無法得知它是否成功執行。
finalize會導致嚴重的性能損失(在原書作者的機器上,有finalize方法時創建和銷毀對象慢了大約430倍)。
因此,鑒于finalize方法有如此多的缺點,我們大部分時候應當避免使用。但仍有兩種合理用法。
用途一:配合顯式終止方法
仍然是關閉文件的問題,正確的做法是提供一個顯式終止方法,通常稱為close
,并要求客戶端程序手動調用。該方案的典型例子是InputStream
、OutputStream
、java.sql.Connection
,這些類都提供了close
方法以關閉相應的文件或數據庫連接。
但是一些不靠譜的程序員通常會忘記調用close
方法,因此可以在finalize中再次檢查并調用close
方法,以降低資源泄漏的可能性。(注意,由于finalize并不可靠,該方案只能降低資源泄漏的可能性而無法完全消除。)在InputStream
、OutputStream
、java.sql.Connection
這些類中也的確是這樣做的,大家可以自己查看源碼。
用途二:終結方法守衛者
如果子類打算重寫父類的finalize方法,我們推薦以下面的方式重寫。這樣做可以保證即使子類的終結過程拋出異常,父類的finalize方法也會得到執行。
@Override
protected void finalize() throws Throwable {
try {
//Finalize subclass state
} finally {
super.finalize();
}
}
但是,假如子類沒有在重寫的finalize方法中調用super.finalize()
,那么父類的finalize方法將永遠不能得到調用。站在父類的角度上,要想防范這樣粗心或者惡意的子類,可以使用終結方法守衛者。
終結方法守衛者是在父類中的一個匿名內部類實例,該內部類覆寫finalize方法,并在其中做父類需要的終結操作。
public class Foo {
private final Object finalizerGuardian = new Object() {
@Override
protected void finalize() throws Throwable {
//Finalize outer Foo object
}
};
//...
}
現在,即使Foo的子類覆寫了finalize方法,也不會影響Foo的終結操作,因為finalizerGuardian
一直存在。當子類對象不可達時,finalizerGuardian
同樣不可達,它們都會進入垃圾回收器的回收隊列。因此可以保證父類的終結操作不被忽略。
關注作者或文集《Effective Java》,第一時間獲取最新發布文章。
參考資料
Java終結方法的使用(終結守衛者) SamXCode
What's the behaviors of fields when finalize an object? jinge
Java將棄用finalize()方法? Ben Evans