java finalize方法
程 序员都了解初始化的重要性,但常常会忘记同样重要的清除工作。毕竟,谁需要清除一个int 呢?但在使用程序库时,把一个对象用完后就“弃之不顾”的做法并非总是安全的。当然,Java有垃圾回收器来回收无用对象占据的内存资源。但也有特殊情 况:假定你的对象(并非使用 new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由 new分配的内存,所以它不知道该如何释放该对象的这块 “特殊”内存。 这里有一个潜在的编程陷阱,因为有些程序员(特别是 C++程序员)刚开始可能会误把finalize( )当作C++中的“析构函数”(C++中销毁对象必须用到这个函数)。所以有必要明确区分一下:在 C++中,对象一定会被“销毁”(如果程序中没有错误的话);而 Java 里的对象却并非总是被“垃圾回收”的。或者换句话说: |
1. 对象可能不被回收。
2. 垃圾回收并不等于“析构”。
牢记这些,你就能远离困扰。这意味着在你不再需要某个对象之前,如果必须执行某些动作,那么你得自己去 做。Java并未提供“析构函数”或相似的概念,要做类似的清除工作,你必须自己动手创建一个执行清除工作的普通方法。例如,假设某个对象在创建过程中, 会将自己绘制到屏幕上。要是你不明确地从屏幕上将其擦除,它可能永远得不到清除。如果在
finalize( )里加入某种擦除功能,当“垃圾回收”发生时(不能保证一定会发生),finalize( )得到了调用,图像就会被擦除。要是“垃圾回收”没有发生,图像就会一直保留下来。
也许你会发现,只要程序没有濒临存储空间用完的那一刻,对象占用的空间就总也得不到释放。如果程序执行结 束,并且垃圾回收器一直都没有释放你创建的任何对象的存储空间,则随着程序的退出,那些资源会全部交还给操作系统。这个策略是恰当的,因为垃圾回收本身也 有开销,要是不使用它,那就不用支付这部分开销了。
finalize( )用途何在?
此时,你已经明白了不该将finalize( )作为通用的清除方法。那么,finalize( )的真正用途是什么呢?
这引出了要记住的第三点:
3.垃圾回收只与内存有关。
也就是说,垃圾回收器存在的唯一原因是为了回收程序不再使用的内存。所以对于与垃圾回收有关的任何行为来说(尤其是finalize( )方法),它们也必须同内存及其回收有关。
但这是否意味着要是对象中含有其他对象,finalize( )就应该明确释放那些对象呢?不——无论对象是如何创建的,垃圾回收器都会负责释放对象占据的所有内存。这就将对 finalize( )的需求限制到特殊情况之下:你通过某种非“创建对象”的方式为对象分配了存储空间。不过,你也看到了,Java中一切皆为对象,那这种特殊情况是怎么回 事呢?
看来之所以要有finalize( ),是由于你可能在分配内存时,采用了类似C语言中的做法而非Java中的通常做法。这种情况主要发生在使用“本地方法”的情况下,它是在Java中调用 非Java代码的一种方式。本地方法目前只支持C和C++。但它们可以调用其它语言写的代码,所以你实际上可以调用任何代码。在非Java代码中,也许会 调用类似C的malloc( )函数,用它分配存储空间,而且除非调用了free( )函数,否则存储空间将不会得到释放,从而造成内存泄露。当然,free( )是C和C++中的函数,所以你需要在finalize( )中用本地方法调用它。
至此,你或许已经明白了不要过多地使用finalize( )的道理了。对,它确实不是进行普通的清除工作的合适场所。那么,普通的清除工作应该在哪执行呢?
你必须执行清除
为清除一个对象,用户必须在进行清除的时刻调用执行清除动作的方法。听起来似乎很简单,但却与C++中的 “析构函数”的概念稍有抵触。在C++ 中,所有对象都会被销毁,或者说, “应该”被销毁。如果在C++中创建了一个局部对象(就是在堆栈上创建,Java中可不行),此时的销毁动作发生在以“右花括号”为边界的、此对象作用域 的末尾处进行。如果对象是
用new创建的(类似于Java),那么当程序员调用C++的delete( )时(Java没有这个命令),就会调用相应的析构函数。如果程序员忘了,那么永远不会调用析构函数,就会出现内存泄露,对象的其他部分也不会得到清除。 这种错误很难跟踪,这也是让 C++程序员转向 Java的一个主要因素。
相反,Java不允许创建局部对象,你必须使用new。在Java中,也没有“delete”来释放对 象,因为垃圾回收器会帮助你释放存储空间。甚至可以肤浅地认为,正是由于垃圾收集机制的存在,使得 Java 没有析构函数。然而,随着学习的深入,你就会明白垃圾回收器的存在并不能完全代替析构函数。(而且你绝对不能直接调用finalize( ),所以这也不是一个恰当的
途径。)如果你希望进行除释放存储空间之外的清除工作,你还是得明确调用某个恰当的Java方法。这就等同于使用析构函数了,而且没有它方便。
记住,无论是“垃圾回收”还是“终结”,都不保证一定会发生。如果Java虚拟机(JVM)并未面临内存耗尽的情形,它是不会浪费时间在回收垃圾以恢复内存上的。
终结条件
通常,你不能指望finalize( ),你必须创建其它的“清除”方法,并且明确地调用它们。看来,finalize( )只能存在于程序员很难用到的一些晦涩用法里了。不过,finalize( )还有一个有趣的用法,它并不依赖于每次都要对finalize( )进行调用,这就是对象“终结条件”的验证。
当你对某个对象不再感兴趣,也就是它可以被清除时,这个对象应该处于某种状态,使它占用的内存可以被安全 地释放。例如,要是对象代表了一个打开的文件,在对象被回收前程序员应该关闭这个文件。只要对象中存在没有被适当清除的部分,你的程序就存在很隐晦的错 误。finalize( )的价值在于可以用来最终发现这种情况,尽管它并不总是会被调用。如果某次
finalize( )的动作使得bug被发现,那你就可据此找出问题所在——这才是你真正关心的。
以下是个简单的例子,示范了可能的使用方式:
class Book { boolean checkedOut = false; Book(boolean checkOut) { checkedOut = checkOut; } void checkIn() { checkedOut = false; } public void finalize() { if(checkedOut) System.out.println("Error: checked out"); // Normally, you'll also do this: // super.finalized(); } } public class TerminationCondition { public static void main(String[] args) { Book novel = new Book(true); // Proper cleanup: novel.checkIn(); // Drop the reference, forget to clean up: new Book(true); // Force garbage collection & finalization: System.gc(); } }
本例的终结条件是:所有的Book对象在被当作垃圾回收前都应该被签入(check in)。但在main( )方法中,由于程序员的错误,有一本书未被签入。要是没有 finalize( )来验证终结条件,将很难发现这种错误。
注意,System.gc( )用于强制终结动作的进行。即使不这么做的话,通过重复的执行程序(假设程序将分配大量的存储空间而导致垃圾回收动作的执行),最终也能找出错误的Book对象。
引用:http://www.java3z.com/cwbwebhome/article/article8/83463.html?id=4388