领略finalize()-析构函数的替代者
副标题#e#
在很多方面,Java 雷同于 C++。Java 的语法很是雷同于 C++,Java 有类、要领和数据成员;Java 的类有结构函数; Java 有异常处理惩罚。
可是,假如你利用过 C++ 会发明 Java 也丢掉一些大概是你熟悉的特性。这些特性之一就是析构函数。代替利用析构函数,Java 支持finalize() 要领。
在本文中,我们将描写 finalize() 与 C++ 析构函数的区别。别的,我们将建设一个简朴的 Applet 来演示 finalize() 是如何事情的。
最终的边界
与 Java 差异,C++ 支持局部工具(基于栈)和全局工具(基于堆)。因为这一双重支持,C++ 也提供了自动结构和析构,这导致了对结构函数和析构函数的挪用,(对付堆工具)就是内存的分派和释放。
在 Java 中,所有工具都驻留在堆内存,因此局部工具就不存在。功效,Java 的设计者以为不需要析构函数(象 C++ 中所实现的)。
取而代之,Java 界说了一个非凡的要领叫做finalize() ,它提供了 C++ 析构函数的一些成果。可是,finalize() 并不完全与 C++ 的析构函数一样,并可以假设它会导致一系列的问题。finalize() 要领浸染的一个要害元素是 Java 的垃圾接纳器。
垃圾接纳器
在 C/C++、Pascal和其他几种多种用途的编程语言中,开拓者有责任在内存打点上发挥努力的浸染。譬喻,假如你为一个工具或数据布局分派了内存,那么当你不再利用它时必需释放掉该内存。
在 Java 中,当你建设一个工具时,Java 虚拟机(JVM)为该工具分派内存、挪用结构函数并开始跟踪你利用的工具。当你遏制利用一个工具(就是说,当没有对该工具有效的引用时),JVM 通过垃圾接纳器将该工具标志为释放状态。
当垃圾接纳器将要释放一个工具的内存时,它挪用该工具的finalize() 要领(假如该工具界说了此要领)。垃圾接纳器以独立的低优先级的方法运行,只有当其他线程挂起期待该内存释放的环境呈现时,它才开始运行释放工具的内存。(事实上,你可以挪用System.gc() 要领强制垃圾接纳器来释放这些工具的内存。)
在以上的描写中,有一些重要的工作需要留意。首先,只有当垃圾接纳器释放该工具的内存时,才会执行finalize()。假如在 Applet 或应用措施退出之前垃圾接纳器没有释放内存,垃圾接纳器将不会挪用finalize()。
其次,除非垃圾接纳器认为你的 Applet 或应用措施需要特另外内存,不然它不会试图释放不再利用的工具的内存。换句话说,这是完全大概的:一个 Applet 给少量的工具分派内存,没有造成严重的内存需求,于是垃圾接纳器没有释放这些工具的内存就退出了。
显然,假如你为某个工具界说了finalize() 要领,JVM 大概不会挪用它,因为垃圾接纳器未曾释放过那些工具的内存。挪用System.gc() 也不会起浸染,因为它仅仅是给 JVM 一个发起而不是呼吁。
finalize() 有什么利益呢?
假如finalize() 不是析构函数,JVM 不必然会挪用它,你大概会迷惑它是否在任何环境下都有长处。事实上,在 Java 1.0 中它并没有太多的利益。
按照 Java 文档,finalize() 是一个用于释放非 Java 资源的要领。可是,JVM 有很大的大概不挪用工具的finalize() 要领,因此很难证明利用该要领释放资源是有效的。
Java 1.1 通过提供一个System.runFinalizersOnExit() 要领部门地办理了这个问题。(不要将这个要领与 Java 1.0 中的System.runFinalizations() 要领相夹杂。)不象System.gc() 要领那样,System.runFinalizersOnExit() 要领并不当即试图启动垃圾接纳器。而是当应用措施或 Applet 退出时,它挪用每个工具的finalize() 要领。
正如你大概揣摩的那样,通过挪用System.runFinalizersOnExit() 要领强制垃圾接纳器排除所有独立工具的内存,当排除代码执行时大概会引起明明的延迟。此刻成立一个示例 Applet 来演示 Java 垃圾接纳器和finalize() 要领是如何彼此浸染的。
#p#副标题#e#
接纳垃圾
通过利用Java Applet Wizard 建设一个新的 Applet 开始。当提示这样做时,输入 final_things 作为 Applet 名,并选择不要生成源文件注释。
接下来,在Java Applet Wizard 举办第三步,不要选择多线程选项。在第五步之前,按照需要修改 Applet 的描写。
当你单击Finish 后,Applet Wizard 将生成一个新的事情空间,并为该项目建设缺省的 Java 文件。从列表 A 中选择适当的代码输入(我们已经突出显示了你需要输入的代码)。
当你完成代码的输入后,设置Internet 欣赏器将System.out 的输出信息写到Javalog.txt 文件中。(在IE 选项对话框的高级页面中选择起用 Java Logging。)
#p#分页标题#e#
编译并运行该 Applet。然后,期待 Applet 运行(你将在状态栏中看到 Applet 已启动的信息),退出欣赏器,并打开Javalog.txt 文件。你将会发明雷同于下列行的信息:
1000 things constructed
0 things finalized
正如你可以或许看到的那样,成立了1,000个工具仍然没有迫使垃圾接纳器开始接纳空间,纵然在 Applet 退出时也没有工具被利用。
此刻,删除在stop() 要领第一行中的注释符以起用System.gc() 要领。再次编译并运行该 Applet ,期待 Applet 完成运行,并退出欣赏器。当你再次打开Javalog.txt 文件,你将看到下列行:
1000 things constructed
963 things finalized
这次,垃圾接纳器认为大大都工具未被利用,并将它们接纳。按顺序,当垃圾接纳器开始释放这些工具的内存时,JVM 挪用它们的finalize() 要领。
担任finalize()?
顺便,假如你在类中界说了finalize() ,它将不会自动挪用基类中的要领。在我们接头了finalize() 与 C++ 的析构函数的差异点后,对这个结论不会惊奇,因为为某个类定制的排除代码另一个类不必然会需要。
假如你抉择要通过派生一个类的finalize() 要领来挪用基类中的finalize() 要领,你可以象其他担任要领一样处理惩罚。
protected void finalize()
{
super.finalize();
// other finalization code...
}
除了答允你节制是否执行排除操纵外,这个技能还使你可以节制当前类的finalize() 要领何时执行。
结论
然而有益的是,Java 的自动垃圾接纳器不会失去均衡。作为便利的价钱,你不得不放弃对系统资源释放的节制。不象 C++ 中的析构函数,Java Applet 不会自动执行你的类中的finalize() 要领。事实上,假如你正在利用 Java 1.0,纵然你试图强制它挪用finalize() 要领,也不能确保将挪用它。
因此,你不应当依靠finalize() 来执行你的 Applet 和应用措施的资源排除事情。取而代之,你该当明晰的排除那些资源或建设一个try…finally 块(或雷同的机制)来实现。
列表 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
" +
"Author: Tim Gooch
" +
"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");
}
}