理解finalize()-析構(gòu)函數(shù)的替代者
by Tim Gooch
在許多方面,Java 類似于 C++。Java 的語法非常類似于 C++,Java 有類、方法和數(shù)據(jù)成員;Java 的類有構(gòu)造函數(shù); Java 有異常處理。
但是,如果你使用過 C++ 會發(fā)現(xiàn) Java 也丟掉一些可能是你熟悉的特性。這些特性之一就是析構(gòu)函數(shù)。取代使用析構(gòu)函數(shù),Java 支持finalize() 方法。
在本文中,我們將描述 finalize() 與 C++ 析構(gòu)函數(shù)的區(qū)別。另外,我們將創(chuàng)建一個簡單的 Applet 來演示 finalize() 是如何工作的。
最終的界限
與 Java 不同,C++ 支持局部對象(基于棧)和全局對象(基于堆)。因?yàn)檫@一雙重支持,C++ 也提供了自動構(gòu)造和析構(gòu),這導(dǎo)致了對構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用,(對于堆對象)就是內(nèi)存的分配和釋放。
在 Java 中,所有對象都駐留在堆內(nèi)存,因此局部對象就不存在。結(jié)果,Java 的設(shè)計(jì)者覺得不需要析構(gòu)函數(shù)(象 C++ 中所實(shí)現(xiàn)的)。
取而代之,Java 定義了一個特殊的方法叫做finalize() ,它提供了 C++ 析構(gòu)函數(shù)的一些功能。但是,finalize() 并不完全與 C++ 的析構(gòu)函數(shù)一樣,并可以假設(shè)它會導(dǎo)致一系列的問題。finalize() 方法作用的一個關(guān)鍵元素是 Java 的垃圾回收器。
垃圾回收器
在 C/C++、Pascal和其他幾種多種用途的編程語言中,開發(fā)者有責(zé)任在內(nèi)存管理上發(fā)揮積極的作用。例如,如果你為一個對象或數(shù)據(jù)結(jié)構(gòu)分配了內(nèi)存,那么當(dāng)你不再使用它時必須釋放掉該內(nèi)存。
在 Java 中,當(dāng)你創(chuàng)建一個對象時,Java 虛擬機(jī)(JVM)為該對象分配內(nèi)存、調(diào)用構(gòu)造函數(shù)并開始跟蹤你使用的對象。當(dāng)你停止使用一個對象(就是說,當(dāng)沒有對該對象有效的引用時),JVM 通過垃圾回收器將該對象標(biāo)記為釋放狀態(tài)。
當(dāng)垃圾回收器將要釋放一個對象的內(nèi)存時,它調(diào)用該對象的finalize() 方法(如果該對象定義了此方法)。垃圾回收器以獨(dú)立的低優(yōu)先級的方式運(yùn)行,只有當(dāng)其他線程掛起等待該內(nèi)存釋放的情況出現(xiàn)時,它才開始運(yùn)行釋放對象的內(nèi)存。(事實(shí)上,你可以調(diào)用System.gc() 方法強(qiáng)制垃圾回收器來釋放這些對象的內(nèi)存。)
在以上的描述中,有一些重要的事情需要注意。首先,只有當(dāng)垃圾回收器釋放該對象的內(nèi)存時,才會執(zhí)行finalize()。如果在 Applet 或應(yīng)用程序退出之前垃圾回收器沒有釋放內(nèi)存,垃圾回收器將不會調(diào)用finalize()。
其次,除非垃圾回收器認(rèn)為你的 Applet 或應(yīng)用程序需要額外的內(nèi)存,否則它不會試圖釋放不再使用的對象的內(nèi)存。換句話說,這是完全可能的:一個 Applet 給少量的對象分配內(nèi)存,沒有造成嚴(yán)重的內(nèi)存需求,于是垃圾回收器沒有釋放這些對象的內(nèi)存就退出了。
顯然,如果你為某個對象定義了finalize() 方法,JVM 可能不會調(diào)用它,因?yàn)槔厥掌鞑辉尫胚^那些對象的內(nèi)存。調(diào)用System.gc() 也不會起作用,因?yàn)樗鼉H僅是給 JVM 一個建議而不是命令。
finalize() 有什么優(yōu)點(diǎn)呢?
如果finalize() 不是析構(gòu)函數(shù),JVM 不一定會調(diào)用它,你可能會疑惑它是否在任何情況下都有好處。事實(shí)上,在 Java 1.0 中它并沒有太多的優(yōu)點(diǎn)。
根據(jù) Java 文檔,finalize() 是一個用于釋放非 Java 資源的方法。但是,JVM 有很大的可能不調(diào)用對象的finalize() 方法,因此很難證明使用該方法釋放資源是有效的。
Java 1.1 通過提供一個System.runFinalizersOnExit() 方法部分地解決了這個問題。(不要將這個方法與 Java 1.0 中的System.runFinalizations() 方法相混淆。)不象System.gc() 方法那樣,System.runFinalizersOnExit() 方法并不立即試圖啟動垃圾回收器。而是當(dāng)應(yīng)用程序或 Applet 退出時,它調(diào)用每個對象的finalize() 方法。
正如你可能猜測的那樣,通過調(diào)用System.runFinalizersOnExit() 方法強(qiáng)制垃圾回收器清除所有獨(dú)立對象的內(nèi)存,當(dāng)清除代碼執(zhí)行時可能會引起明顯的延遲?,F(xiàn)在建立一個示例 Applet 來演示 Java 垃圾回收器和finalize() 方法是如何相互作用的。
回收垃圾
通過使用Java Applet Wizard 創(chuàng)建一個新的 Applet 開始。當(dāng)提示這樣做時,輸入 final_things 作為 Applet 名,并選擇不要生成源文件注釋。
接下來,在Java Applet Wizard 進(jìn)行第三步,不要選擇多線程選項(xiàng)。在第五步之前,根據(jù)需要修改 Applet 的描述。
當(dāng)你單擊Finish 后,Applet Wizard 將生成一個新的工作空間,并為該項(xiàng)目創(chuàng)建缺省的 Java 文件。從列表 A 中選擇適當(dāng)?shù)拇a輸入(我們已經(jīng)突出顯示了你需要輸入的代碼)。
當(dāng)你完成代碼的輸入后,配置Internet 瀏覽器將System.out 的輸出信息寫到Javalog.txt 文件中。(在IE 選項(xiàng)對話框的高級頁面中選擇起用 Java Logging。)
編譯并運(yùn)行該 Applet。然后,等待 Applet 運(yùn)行(你將在狀態(tài)欄中看到 Applet 已啟動的信息),退出瀏覽器,并打開Javalog.txt 文件。你將會發(fā)現(xiàn)類似于下列行的信息:
1000 things constructed
0 things finalized
正如你能夠看到的那樣,建立了1,000個對象仍然沒有迫使垃圾回收器開始回收空間,即使在 Applet 退出時也沒有對象被使用。
現(xiàn)在,刪除在stop() 方法第一行中的注釋符以起用System.gc() 方法。再次編譯并運(yùn)行該 Applet ,等待 Applet 完成運(yùn)行,并退出瀏覽器。當(dāng)你再次打開Javalog.txt 文件,你將看到下列行:
1000 things constructed
963 things finalized
這次,垃圾回收器認(rèn)為大多數(shù)對象未被使用,并將它們回收。按順序,當(dāng)垃圾回收器開始釋放這些對象的內(nèi)存時,JVM 調(diào)用它們的finalize() 方法。
繼承finalize()?
順便,如果你在類中定義了finalize() ,它將不會自動調(diào)用基類中的方法。在我們討論了finalize() 與 C++ 的析構(gòu)函數(shù)的不同點(diǎn)后,對這個結(jié)論不會驚訝,因?yàn)闉槟硞€類定制的清除代碼另一個類不一定會需要。
如果你決定要通過派生一個類的finalize() 方法來調(diào)用基類中的finalize() 方法,你可以象其他繼承方法一樣處理。
protected void finalize()
{
super.finalize();
// other finalization code...
}
除了允許你控制是否執(zhí)行清除操作外,這個技術(shù)還使你可以控制當(dāng)前類的finalize() 方法何時執(zhí)行。
結(jié)論
然而有益的是,Java 的自動垃圾回收器不會失去平衡。作為便利的代價,你不得不放棄對系統(tǒng)資源釋放的控制。不象 C++ 中的析構(gòu)函數(shù),Java Applet 不會自動執(zhí)行你的類中的finalize() 方法。事實(shí)上,如果你正在使用 Java 1.0,即使你試圖強(qiáng)制它調(diào)用finalize() 方法,也不能確保將調(diào)用它。
因此,你不應(yīng)當(dāng)依靠finalize() 來執(zhí)行你的 Applet 和應(yīng)用程序的資源清除工作。取而代之,你應(yīng)當(dāng)明確的清除那些資源或創(chuàng)建一個try...finally 塊(或類似的機(jī)制)來實(shí)現(xiàn)。
列表 A: final_things.java
import java.applet.*;
import java.awt.*;
class thing
{
public static int thingcount = 0;
public static int thingfinal = 0;
public thing()
{
++thingcount;
}
protected void finalize()
{
++thingfinal;
}
}
public class final_things extends Applet
{
public final_things()
{
}
public String getAppletInfo()
{
return "Name: final_thing\r\n" +
"Author: Tim Gooch\r\n" +
"Created with Microsoft " +
"Visual J++ Version 1.1";
}
public void init()
{
resize(320, 240);
}
public void destroy()
{
}
public void paint(Graphics g)
{
g.drawString("Created with Microsoft" +
"Visual J++ Version 1.1", 10, 20);
}
public void start()
{
while(thing.thingfinal < 1)
{
new thing();
}
}
public void stop()
{
// System.gc();
System.out.println(thing.thingcount +
" things constructed");
System.out.println(thing.thingfinal +
" things finalized");
}
}