11.4.3 對象的finalize()方法簡介
當垃圾回收器將要釋放無用對象的內(nèi)存時,先調(diào)用該對象的finalize()方法。如果在程序終止之前垃圾回收器始終沒有執(zhí)行垃圾回收操作,那么垃圾回收器將始終不會調(diào)用無用對象的finalize()方法。在Java的Object祖先類中提供了protected類型的finalize()方法,因此任何Java類都可以覆蓋finalize()方法,在這個方法中進行釋放對象所占的相關資源的操作。
Java虛擬機的垃圾回收操作對程序完全是透明的,因此程序無法預料某個無用對象的finalize()方法何時被調(diào)用。另外,除非垃圾回收器認為程序需要額外的內(nèi)存,否則它不會試圖釋放無用對象占用的內(nèi)存。換句話說,以下情況是完全可能的:一個程序只占用了少量內(nèi)存,沒有造成嚴重的內(nèi)存需求,于是垃圾回收器沒有釋放那些無用對象占用的內(nèi)存,因此這些對象的finalize()方法還沒有被調(diào)用,程序就終止了。
程序即使顯式調(diào)用System.gc()或Runtime.gc()方法,也不能保證垃圾回收操作一定執(zhí)行,因此不能保證無用對象的finalize()方法一定被調(diào)用。
11.4.4 對象的finalize()方法的特點
對象的finalize()方法具有以下特點:
垃圾回收器是否會執(zhí)行該方法及何時執(zhí)行該方法,都是不確定的。
finalize()方法有可能使對象復活,使它恢復到可觸及狀態(tài)。
垃圾回收器在執(zhí)行finalize()方法時,如果出現(xiàn)異常,垃圾回收器不會報告異常,程序繼續(xù)正常運行。
下面結合一個具體的例子來解釋finalize()方法的特點。例程11-13的Ghost類是一個帶實例緩存的不可變類,它的finalize()方法能夠把當前實例重新加入到實例緩存ghosts中。
例程11-13 Ghost.java
import java.util.Map;
import java.util.HashMap;
public class Ghost {
private static final Map<String,Ghost> ghosts=new HashMap<String,Ghost>();
private final String name;
public Ghost(String name) {
this.name=name;
}
public String getName(){return name;}
public static Ghost getInstance(String name){
Ghost ghost =ghosts.get(name);
if (ghost == null) {
ghost=new Ghost(name);
ghosts.put(name,ghost);
}
return ghost;
}
public static void removeInstance(String name){
ghosts.remove(name);
}
protected void finalize()throws Throwable{
ghosts.put(name,this);
System.out.println("execute finalize");
//throw new Exception("Just Test");
}
public static void main(String args[])throws Exception{
Ghost ghost=Ghost.getInstance("IAmBack"); //①
System.out.println(ghost); //②
String name=ghost.getName(); //③
ghost=null; //④
Ghost.removeInstance(name); //⑤
System.gc(); //⑥
//把CPU讓給垃圾回收線程
Thread.sleep(3000); //⑦
ghost=Ghost.getInstance("IAmBack"); //⑧
System.out.println(ghost); //⑨
}
}
運行以上Ghost類的main()方法,一種可能的打印結果為:
Ghost@3179c3
execute finalize
Ghost@3179c3
以上程序創(chuàng)建了3個對象:1個Ghost對象、1個常量字符串“IAmBack”及1個HashMap對象。當程序執(zhí)行完main()方法的第③行時,內(nèi)存中引用變量與對象之間的關系如圖11-9所示。
圖11-9 Ghost對象與其他對象及引用變量的關系
當執(zhí)行完第④行時,ghost變量被置為null,此時Ghost對象依然被ghosts屬性間接引用,因此仍然處于可觸及狀態(tài)。當執(zhí)行完第⑤行時,Ghost對象的引用從HashMap對象中刪除,Ghost對象不再被程序引用,此時進入可復活狀態(tài),即變?yōu)闊o用對象。
第⑥行調(diào)用System.gc()方法,它能提高垃圾回收器盡快執(zhí)行垃圾回收操作的可能性。假如垃圾回收器線程此刻獲得了對CPU的使用權,它將調(diào)用Ghost對象的finalize()方法。該方法把Ghost對象的引用又加入到HashMap對象中,Ghost對象又回到可觸及狀態(tài),垃圾回收器放棄回收它的內(nèi)存。執(zhí)行完第⑧行,ghost變量又引用這個Ghost對象。
假如對finalize()做一些修改,使它拋出一個異常:
protected void finalize()throws Throwable{
ghosts.put(name,this);
System.out.println("execute finalize");
throw new Exception("Just Test");
}
程序的打印結果不變。由此可見,當垃圾回收器執(zhí)行finalize()方法時,如果出現(xiàn)異常,垃圾回收器不會報告異常,也不會導致程序異常中斷。
假如在程序運行中,垃圾回收器始終沒有執(zhí)行垃圾回收操作,那么Ghost對象的finalize()方法就不會被調(diào)用。讀者不妨把第⑥行的System.gc()和第⑦行的Thread.sleep(3000)方法注釋掉,這樣更加可能導致finalize()方法不會被調(diào)用,此時程序的一種可能的打印結果為:
Ghost@3179c3
Ghost@310d42
從以上打印結果可以看出,由于Ghost對象的finalize()方法沒有被執(zhí)行,因此這個Ghost對象在程序運行期間始終沒有復活。當程序第二次調(diào)用Ghost.getInstance("IAmBack")方法時,該方法創(chuàng)建了一個新的Ghost對象。
值得注意的是,以上例子僅僅用于演示finalize()方法的特性,在實際應用中,不提倡用finalize()方法來復活對象。可以把處于可觸及狀態(tài)的對象比做活在陽間的人,把不處于這個狀態(tài)的對象(無用對象)比做到了陰間的人。程序所能看見和使用的是陽間的人,假如閻王經(jīng)常悄悄地讓幾個陰間的人復活,使他們在程序毫不知情的情況下溜回陽間,這只會擾亂程序的正常執(zhí)行流程。
11.4.5 比較finalize()方法和finally代碼塊
在Object類中提供了finalize()方法,它的初衷是用于在對象被垃圾回收器回收之前,釋放所占用的相關資源,這和try…catch…finally語句的finally代碼塊的用途比較相似。但由于垃圾回收器是否會執(zhí)行finalize()方法及何時執(zhí)行該方法,都是不確定的,因此在程序中不能用finalize()方法來完成同時具有以下兩個特點的釋放資源的操作。
必須執(zhí)行。
必須在某個確定的時刻執(zhí)行。
具有以上特點的操作更適合于放在finally代碼塊中。此外,可以在類中專門提供一個用于釋放資源的公共方法,最典型的就是java.io.InputStream和java.io.OutputStream類的close()方法,它們用于關閉輸入流或輸出流。當程序中使用了一個輸入流時,在結束使用前應該確保關閉輸入流。
InputStream in;
try{
InputStream in=new FileInputStream("a.txt");
…
}catch(IOException e){
…
}finally{
try{in.close();}catch(IOException e){…}
}
在多數(shù)情況下,應該避免使用finalize()方法,因為它會導致程序運行結果的不確定性。在某些情況下,finalize()方法可用來充當?shù)诙影踩Wo網(wǎng),當用戶忘記顯式釋放相關資源時,finalize()方法可以完成這一收尾工作。盡管finalize()方法不一定會被執(zhí)行,但是有可能會釋放資源,這總比永遠不會釋放資源更安全。
可以用自動洗衣機的關機功能來解釋finalize()方法的用途。自動洗衣機向用戶提供了專門的關機按鈕,這相當于AutoWasher類的close()方法,假如用戶忘記關機,相當于忘記調(diào)用AutoWasher對象的close()方法,那么自動洗衣機會在洗衣機停止工作后的1個小時內(nèi)自動關機,這相當于調(diào)用finalize()方法。當然,這個例子不是太貼切,因為如果用戶忘記關機,洗衣機的自動關機操作總會被執(zhí)行。