【问题标题】:Clean up code in finalize() or finally()?在 finalize() 或 finally() 中清理代码?
【发布时间】:2009-12-03 23:43:54
【问题描述】:

我一般认为资源清理是在 finally 块中完成的,
最近我在一个类中发现了这个特殊的代码 sn-p,它覆盖了 Object 类的 finalize() 方法。

protected void finalize() {  
    try {
        In.close(); 
        Out.close();
        socket.close();
    }
    catch (Exception e) {
        //logger code here
    }
}

这是个好主意吗? finalize() 相对于finally 的优缺点是什么?

【问题讨论】:

  • finalized() 不应该被使用。如果开发人员忘记 close(),请不要帮助他。让资源泄漏,因此可以注意到并修复问题。 Java 7 将引入更好的资源关闭方式,因此最终将不再有 finalized() 的借口。
  • IIRC,一个线程专用于在各种对象上运行 finalize()。这是让系统在性能方面陷​​入困境的一种方式。

标签: java


【解决方案1】:

finally 块只是一个始终在try 块之后执行的代码块,即使出现异常也是如此。即它在范围内是本地的

finalize() 方法是一种在垃圾回收时清理整个对象的方法。

Java documentation of finalize()

finally解决了不管是否出现异常情况,在一段代码中清理资源的问题…… finalize() 是一种在您的对象不再被使用时清理资源的方法,一旦垃圾收集器确定不再有对该对象的引用。

简而言之,要回答您的问题,例如,如果您要关闭的套接字是对象的成员,您应该在 finalize() 方法中关闭它们,(尽管这是次优的,例如,因为有不能保证 GC 何时真正执行该操作)

但是,如果您在方法中打开套接字,并且在方法结束时完成了它,您应该释放 finally 块中的资源。

【讨论】:

  • 很好 - 一个 Java 问题,有一篇 C# 文章来回答它。请注意,本文中的详细信息可能与 Java 相关,也可能不相关。
  • 我已经更新了 Java 实现的链接。很抱歉造成混乱。
  • @JohnWeldon - 这句话似乎暗示'finally'块仅在异常情况下执行,这是错误的:“finally 仅解决在异常情况下清理资源的问题”。
【解决方案2】:

总是在 finally 中清理东西。

不保证会在 finalize 中进行清理。

但是,如果 finally 块向您抛出另一个异常,通常会在终结器中清理诸如最后安全阀之类的东西。

依赖终结器的真正问题是在 GC 开始调用终结器之前可能需要其他资源。

【讨论】:

  • 它们不能相互替代。
  • 最后并不总是可行的——它们只有在你的代码从资源分配的地方不断地流向它被清理的地方时才会起作用。如果您在打开 GUI 屏幕时分配它并在用户点击“确定”按钮后清理它会怎样?
  • 仅供参考:finalize 将始终在垃圾收集器处理它时被调用。然而,关键是“当垃圾收集器处理它时”。一个完全符合标准的 Java 实现可能不会在宇宙热死之前调用finalize
  • @Chip - JLS 说:“Java 编程语言没有指定终止器将在多长时间内被调用,只是说它会在对象的存储被重用之前发生。”。因此,GC 检测到需要运行终结器是合法的,但根本不运行它……前提是它实际上并未重用对象的内存。
【解决方案3】:

幻影引用会做你想做的事。

只是不要使用 finalize。有一些边缘情况可能会有所帮助(当类被 GC 处理时打印调试信息就派上用场了),但通常不会。 JVM 合约中没有任何内容甚至说它必须被调用。

有一种被称为“参考”的对象非常不为人知。一个是为你认为你会使用 finalize 的事情明确制作的。

Phantom reference 对象,在收集器确定它们的引用对象可能会被回收后排队。”

我突然想到必须有a description of this on the web--所以我将用这个参考替换我刚刚写的所有“操作方法”内容。

【讨论】:

  • 我不知道,因为 finalize 早于引用——他们必须重新实现它(例如,我正在处理的系统没有引用,但它仍然有 finalize)。关键是,您不能依赖 finalize 跨实现被调用。它可能有效,可能有一个命令行开关强制在退出之前调用它,但它依赖于运行时,而不是你可以在编译时控制的东西。幻影引用具有可靠的行为,并且您没有在要清理的对象内执行代码。
【解决方案4】:

它们不相关。这就像在问:“你应该在初始化程序中还是在普通方法中创建对象?”就像,这取决于您对对象所做的事情。终结器在对象被销毁时清理它的状态(也许——这不是你应该依赖的东西),而 finally 块在 try 块之后执行代码。没有任何常见的情况可以让您选择其中一种,因为它们做的事情不同。

【讨论】:

    【解决方案5】:

    如果您的应用程序导致创建大量此类对象,则完成可能不是一个好主意。这是因为当对象符合垃圾回收条件时,finalize 会导致瓶颈。

    有时 finalize 是唯一的解决方案;但尽可能使用 finally

    【讨论】:

      【解决方案6】:

      最后。 Finalize 不好,因为它可能永远不会被调用。仅将 finalize 用作安全网。例如,一个 InputStream 应该有一个 finalize 来关闭流,以防 applcicationforgets 到。但是应用程序应该关闭它。

      如果是我,我也会在终结器中进行清理,并记录执行清理时的情况,然后在应用程序中追踪忘记正确清理的代码。

      【讨论】:

        【解决方案7】:

        问题中的代码有很多问题,包括:

        • 最大的问题:您似乎正试图关闭一个套接字。即使您没有正确关闭它,它也会在自己的终结器中关闭。添加另一个终结器不会使其关闭。
        • 第一个 close 抛出的异常将阻止其他的执行(碰巧,这在本示例中无关紧要,因为 Socket 的特殊行为)。
        • 如果你确实覆盖了finalize,让它抛出Throwable(并添加@Override)。从技术上讲,您还应该在 finally 块中调用 super。
        • Java 内存模型在涉及终结器时非常奇怪(之前的代码执行不一定在终结器执行之前发生)。我会解释这个问题,但你需要知道的是,你需要远离终结者。

        所以:始终使用finally 处理这些事情。 finalize 非常专业(PhantomReference 可能更好但表面上更复杂)。

        【讨论】:

          【解决方案8】:

          如果您正在寻找 finalize() 的替代品,那么正确的问题是:

          • 为什么要使用显式 close() 方法,例如 java.io.* 中的所有流和写入器/读取器类以及许多其他类 - 当存在 finalize() 时?

          其他答案清楚地表明finalize() 的缺点是您无法强制它运行,任何可能使用您的代码的人也没有。

          当然,调用close() 方法(最好在finally 块或close() 方法本身中完成)必须由作者记录,然后记住由使用代码的人调用。但是有很多例子(不仅是java.io.*)是强加的并且是有效的。

          顺便说一句:close() 只是一个约定。

          【讨论】:

            【解决方案9】:

            Joshua Bloch 在他的书Effective Java (2nd Edition) 中提出了非常明确的建议。抄自第二章Item 7: Avoid finalizers:

            终结器是不可预测的,通常很危险,而且通常是不必要的。使用它们会导致行为不稳定、性能不佳和可移植性问题。终结器有一些有效的用途,我们将在本项目后面介绍,但根据经验,您应该避免使用终结器。

            请阅读参考资料以了解原因。

            【讨论】:

              猜你喜欢
              • 2015-10-16
              • 1970-01-01
              • 2013-12-04
              • 2012-05-21
              • 1970-01-01
              • 2011-11-03
              • 1970-01-01
              • 1970-01-01
              • 2014-02-01
              相关资源
              最近更新 更多