一橱健、終結方法VS析構器
熟悉C++的都知道斯撮,析構器是用來回收一個對象所占用資源的常規(guī)方法批幌,是構造器所必需的對應物础锐。在JAVA中,當一個對象變得不可達時荧缘,垃圾回收器會回收與該對象關聯(lián)的存儲空間皆警,這不需要我們操心。對于非內存資源的回收截粗,C++析構器是可以管理的信姓,而JAVA的垃圾回收器是不會管理這些非內存資源的,我們通常使用try-finally塊來顯式管理這些資源绸罗,比如文件操作意推,socket連接等。
二珊蟀、終結方法的缺點
1菊值、行為不穩(wěn)定
終結方法不能保證被及時地執(zhí)行,有時甚至根本不會被執(zhí)行。從一個對象變成不可到達開始腻窒,到它的終結方法被執(zhí)行昵宇,所花費的時間是任意長的。如果在終結方法中執(zhí)行文件關閉操作儿子,很有可能造成程序因為不能打開文件而出現(xiàn)運行錯誤瓦哎,因為終結方法執(zhí)行時間是任意的,而系統(tǒng)文件打開數(shù)是一定的典徊,當系統(tǒng)文件打開數(shù)達到最大時杭煎,程序就會拋出Open too many files異常,這是系統(tǒng)級錯誤卒落,所以不能依靠終結方法來處理這些有限資源的釋放羡铲。另外,也不能依賴終結方法來更新重要的持久狀態(tài)儡毕。例如也切,依賴終結方法解放共享資源(比如數(shù)據(jù)庫)上的永久鎖,很容易讓整個分布式系統(tǒng)垮掉腰湾。
ps:可達性
從強到弱雷恃,不同級別的可達性反應對象的生命周期,定義如下:
如果一個對象可以被一些線程直接使用而不用通過其他引用對象费坊,那么它就是強可達倒槐。一個新創(chuàng)建的對象對創(chuàng)建它的線程來講就是強可達的。
如果一個對象沒有強可達性附井,但是它可以通過一個軟引用(soft reference.)來使用讨越,那么它就具有軟可達性。
如果一個對象既沒有強可達性永毅,也沒有軟可達性把跨,但是它可以通過一個弱引用(weak reference)來使用,那么他就具有弱可達性沼死。當弱引用指向的弱可達對象沒有其他的引用着逐,那么這個對象就會被回收。
如果一個對象既沒有強可達性意蛀,也沒有軟可達性耸别、弱可達性,他已經被finalized县钥,并且有一些虛引用(phantom reference)指向它太雨,那么它就具有虛可達性。
當一個對象不能通過以上的方式指向魁蒜,那么這個對象就變得不可達,并因此適合被回收
2、可移植性問題
及時執(zhí)行終結方法是垃圾回收算法的一個主要功能兜看,而每個JVM實現(xiàn)中锥咸,垃圾回收算法是不一樣的,這就導致終結方法在不同JVM實現(xiàn)下表現(xiàn)的差異细移,這種差異可能帶來災難性影響搏予。
3、性能損失
使用終結方法銷毀對象需要依賴于JVM的垃圾回收算法調度弧轧,這樣必將造成銷毀對象耗時更多雪侥,降低性能。數(shù)據(jù)統(tǒng)計精绎,創(chuàng)建和銷毀一個簡單對象的時間大約為5.6ns速缨,增加一個終結方法使時間增加到2400ns。換句話說代乃,用終結方法創(chuàng)建和銷毀對象慢了大約430倍旬牲。
三、有沒有替代方法?
如果類的對象中封裝的資源確實需要終止搁吓,則可以用下面的方法來替代使用終結方法原茅。只需提供一個顯示的終止方法,并且要求該類的客戶端在每個實例不再有用的時候調用這個方法堕仔。值得提及的一個細節(jié)是擂橘,該實例必須記錄下自己是否已經被終止并了:顯示的終止方法必須在一個私有域中記錄下”該實例不再有效”,可以是一個boolean也可以是其他的摩骨。這樣做為了防止其他方法或者線程來調用這個已經被終結的對象之后造成不可預知的結果通贞。顯示終結方法的典型例子是InputStream、OutputStream和java.sql.Connection上的close方法仿吞。顯示的終止方法通常與try-finally結構結合以來使用滑频,確保及時終止。在finally子句內部調用顯示的終止方法唤冈,可以保證即使在使用對象的時候有異常拋出峡迷,終止方法也會進行的:
public void test() {
FileInputStream fin = null;
try {
fin = new FileInputStream(filename);
//do something.
} finally {
fin.close();
}
}
終結方法有什么好處?
它們有兩種合法的用途。第一種用途是你虹,當對象的所有者忘記調用前面段落中建議的顯示終結方法時绘搞,可以充當“安全網”的作用,做好第二道防御的措施傅物。雖然這樣做并不能保證會及時的調用夯辖,但是遲一點執(zhí)行總比沒有執(zhí)行好吧
終結方法第二種合理的用途與對象的本地對等體(native peer)有關。本地對等體是一個本地對象(native object),普通對象通過本地方法委托給一個本地對象董饰。因為本地對等體不是一個普通的Java對象蒿褂,所以垃圾回收器并不會知道它圆米,當它的Java對等體被回收的時候,它不會被回收啄栓。在本地對等體并不擁有關鍵資源的前提下娄帖,終結方法正是執(zhí)行這項任務最合適的工具。如果本地對等體擁有必須被馬上終止的資源昙楚,那么該類就應該有一個顯示的終止方法近速。
值得注意的是,“終結方法鏈”并不會被自動的執(zhí)行堪旧。如果類有終結方法削葱,并且子類覆蓋了終結方法,則需要手動的去調用超類的終結方法淳梦∥鲈遥可以這么使用來確保父類的終結方法也會得到調用:
@Override
protected void finalize() throws Throwable {
try {
// .....
} finally {
super.finalize();
}
}
如果子類覆蓋了超類的終結方法,但是忘了手動的調用終結方法谭跨,那么超類的終結方法將永遠也不會被調用到干厚。要防范這樣粗心大意或者惡意的子類也是有可能的,代價就是為每個將被終結的對象創(chuàng)建一個附加的對象螃宙。不是把終結方法放在要求終結處理的類中蛮瞄,而是放在一個匿名的類中,該匿名類的唯一用途就是終結它的外圍實例(enclosing instance)谆扎。該匿名類的單個實例被成為終結方法守衛(wèi)者(finalizer guardian)挂捅,外圍類的每個實例都會創(chuàng)建這樣一個守衛(wèi)者。外圍實例在私有實例域中保存著一個對終結方法守衛(wèi)者的唯一引用堂湖,因此終結方法守衛(wèi)者與外圍實例可以同時啟動終結過程闲先。當守衛(wèi)者被終結的時候,它執(zhí)行外圍實例所期望的終結行為无蜂,就好像它是終結方法外圍讓對象上的一個方法一樣伺糠。
public class Parent {
public static void main(String[] args){
doSth();
System.gc();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void doSth() {
Child c = new Child();
System.out.println(c);
}
private final Object guardian = new Object(){
@Override
protected void finalize(){
System.out.println("執(zhí)行父類中匿名內部類--終結方法守衛(wèi)者,重寫的finalize()");
// 在這里調用Parent重寫的finalize即可在清理子類時調用父類自己的清理方法
parentlFinalize();
}
};
protected void parentlFinalize() {
System.out.println("執(zhí)行父類自身的終結方法");
}
}
class Child extends Parent {
@Override
protected void finalize() {
System.out.println("執(zhí)行子類finalize方法,注意斥季,這里子類并沒有調用super.finalize()");
// 由于子類(忘記或者其他原因)沒有調用super.finalize()
// 使用終結方法守衛(wèi)者可以保證子類執(zhí)行finalize()時(沒有調用super.finalize())训桶,父類的清理方法仍舊調用
}
}
總之除非是作為資源回收處理的第二道防線(安全網)或者是為了終結非關鍵的資源,否則請不要使用終結方法酣倾。如果沒辦法真的使用了finalize舵揭,別忘記了調用super.finalize()。還應考慮是否使用終結方法守衛(wèi)者躁锡,使未調用super.finalize()方法的類的父類的終結方法也會被執(zhí)行午绳。