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