文档库 最新最全的文档下载
当前位置:文档库 › finalize方法使用

finalize方法使用

本文由sihaiwubian123贡献
doc文档可能在WAP端浏览体验不佳。建议您优先选择TXT,或下载源文件到本机查看。
程序员都了解初始化的重要性,但常常会忘记同样重要的清除工作。毕竟,谁需要清除一个 int 呢?但在使用程序库时, 把一个对象用完后就“弃之不顾”的做法并非总是安全的。 当然, Java 有垃圾回收器来回收无用对象占据的内存资源。但也有特殊情况:假定你的对象(并 非使用 new)获得了一块“特殊”的内存区域,由于垃圾回收器只知道释放那些经由 new 分 配的内存,所以它不知道该如何释放该对象的这块 “特殊”内存。为了应对这种情况,Java 允许你在类中定义一个名为 finalize( )的方法。 它的工作原理“应该”是这样的: 一旦垃圾回收 器准备好释放对象占用的存储空间,将首先调用其 finalize( )方法,并且在下一次垃圾回收 动作发生时,才会真正回收对象占用的内存。所以要是你打算用 finalize( ),就能在“垃圾回 收时刻”做一些重要的清除工作。 这里有一个潜在的编程陷阱,因为有些程序员(特别是 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 对象。 1. 垃圾收集器的工作目标是回收已经无用的对象的内存空间, 从而避免内存渗漏体的产生, 节省内存资源,避免程序代码的崩溃。 2.垃圾收集器判断一个对象的内存空间是否无用的标准是:如果该对象不能再被程序中任 何一个"活动的部分"所引用,此时我们就说,该对象的内存空间已经无用。所谓"

活动的部 分",是指程序中某部分参与程序的调用,正在执行过程中,尚未执行完毕。 3.垃圾收集器线程虽然是作为低优先级的线程运行,但在系统可用内存量过低的时候,它 可能会突发地执行来挽救内存资源。当然其执行与否也是不可预知的。 4. 垃圾收集器不可以被强制执行, 但程序员可以通过调用 System. gc 方法来建议执行垃圾 收集器。 5.不能保证一个无用的对象一定会被垃圾收集器收集,也不能保证垃圾收集器 在一段 Java 语言代码中一定会执行。因此在程序执行过程中被分配出去的内存空间可能会 一直保留到该程序执行完毕,除非该空间被重新分配或被其他方法回收。由此可见,完全彻 底地根绝内存渗漏体的产生也是不可能的。但是请不要忘记,Java 的垃圾收集器毕竟使程 序员从手工回收内存空间的繁重工作中解脱了出来。设想一个程序员要用 C 或 C++来编写 一段 10 万行语句的代码,那么他一定会充分体会到 Java 的垃圾收集器的优点! 6.同样没有办法预知在一组均符合垃圾收集器收集标准的对象中,哪一个会被首先收集。
7.循环引用对象不会影响其被垃圾收集器收集。 8.可以通过将对象的引用变量(reference variables,即句柄 handles)初始化为 null 值, 来暗示垃圾收集器来收集该对象。但此时,如果该对象连接有事件监听器(典型的 AWT 组 件) ,那它还是不可以被收集。所以在设一个引用变量为 null 值之前,应注意该引用变量指 向的对象是否被监听,若有,要首先除去监听器,然后才可以赋空值。 9.每一个对象都有一个 finalize( )方法,这个方法是从 Object 类继承来的。 10.finalize( )方法用来回收内存以外的系统资源,就像是文件处理器和网络连接器。该方 法的调用顺序和用来调用该方法的对象的创建顺序是无关的。 换句话说, 书写程序时该方法 的顺序和方法的实际调用顺序是不相干的。请注意这只是 finalize( )方法的特点。 11 .每个对象只能 调用 finalize( )方法一次。如果 在 finalize( )方法执行时产生 异常 (exception) ,则该对象仍可以被垃圾收集器收集。 12.垃圾收集器跟踪每一个对象,收集那些不可到达的对象(即该对象没有被程序的任何" 活的部分"所调用) ,回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用 finalize( )方法,通过让其他对象知道它的存在,而使不可到达的对象再次"复苏"为可到达的 对象。既然每个对象只能调用一次 finalize( )方法,所以每个对象也只可能"复苏"一次。 13.finalize( )方法可以明确地被调用,但它却不能进行垃圾收集。 14.finalize( )方法可以被重载(overload)

,但只有具备初始的 finalize( )方法特点的方法才 可以被垃圾收集器调用。 15.子类的 finalize( )方法可以明确地调用父类的 finalize( )方法,作为该子类对象的最后一 次适当的操作。但 Java 编译器却不认为这是一次覆盖操作(overriding) ,所以也不会对其 调用进行检查。 16.当 finalize( )方法尚未被调用时, System. runFinalization( )方法可以用来调用 finalize( ) 方法,并实现相同的效果,对无用对象进行垃圾收集。 17.当一个方法执行完毕,其中的局部变量就会超出使用范围,此时可以被当作垃圾收集, 但以后每当该方法再次被调用时,其中的局部变量便会被重新创建。 18.Java 语言使用了一种"标记交换区的垃圾收集算法"。该算法会遍历程序中每一个对象 的句柄,为被引用的对象做标记,然后回收尚未做标记的对象。所谓遍历可以简单地理解为 "检查每一个"。 19.Java 语言允许程序员为任何方法添加 finalize( )方法,该方法会在垃圾收集器交换回收 对象之前被调用。 但不要过分依赖该方法对系统资源进行回收和再利用, 因为该方法调用后 的执行结果是不可预知的。 通过以上对垃圾收集器特点的了解,你应该可以明确垃圾收集 器的作用,和垃圾收集器判断一块内存空间是否无用的标准。简单地说,当你为一个对象赋 值为 null 并且重新定向了该对象的引用者,此时该对象就符合垃圾收集器的收集标准。

相关文档
相关文档 最新文档