7

假设我创建了一些资源类,其中包含用于清理资源的 close() 方法,如果有人忘记调用 close(),我想重写 finalize() 以释放资源(并打印警告)。如何正确地做到这一点?

  • 是否仅推荐用于本机(JNI 分配的)资源?
  • 如果你从终结器中使用对另一个已经终结的对象的引用会发生什么?如果存在循环依赖关系,我看不出垃圾收集器如何阻止您访问可能已执行终结器的对象。
  • 是否有更好的替代方法来覆盖 finalize() 来检测和/或处理资源泄漏?
  • 在实现终结器时还有其他需要注意的陷阱吗?

注意:我知道使用 finalize() 通常是个坏主意,并且不能保证会被调用,还有其他几个问题在讨论这个问题。这个问题专门关于如何在 Java 中实现终结器,而不是关于为什么应该(或不应该)。

4

2 回答 2

6

有效的 java (2nd edition)中,Joshua 在 Item #7 中详细介绍了如何做到这一点。他首先建议你几乎不应该使用finalizers。但是,使用它的一个原因是仅打印一条日志语句,说明您有资源泄漏。他说这样做的一个缺点是有人可以扩展你的类而不正确地调用超级终结器。所以他建议在子类中做这样的事情:

// Manual finalizer chaining
   @Override protected void finalize() throws Throwable {
       try {
           ... // Finalize subclass state
       } finally {
           super.finalize();
   } 
}

这是为了确保如果当前类中出现问题,finally仍然会调用它。这可能是一个糟糕的解决方案,因为它取决于将您的类子类化的人。另一种解决方案是使用监护人目标文件来完成这项工作。这看起来像:

// Finalizer Guardian idiom
   public class Foo {
// Sole purpose of this object is to finalize outer Foo object
      private final Object finalizerGuardian = new Object() {
         @Override protected void finalize() throws Throwable {
            ... // Finalize outer Foo object
         }
      };
      ...  // Remainder omitted
}

这是一种更简洁的方法,因为您知道没有人可以覆盖该功能。

关闭资源的建议方法仍在实施Closeable,并确保由用户自行关闭。正如 Josuha 建议的那样,您不应该在该finalize方法中执行任何时间敏感操作。JVM 可能会选择在将来的某个时间运行它。如果您依赖这种方法来进行提交或一些重要的事情,那么这是一个坏主意。

于 2012-09-17T15:23:54.673 回答
1

是否仅推荐用于本机(JNI 分配的)资源?

不,您的用例对于终结器也是有效的。我的意思是记录资源泄漏。

如果在终结器中访问另一个已终结的 Java 对象会发生什么?

如果您仍然可以访问它,它还没有最终确定。或者,也许我在你的问题中遗漏了一些东西。

我现在知道了。可能是 A 和 B 都有资格进行垃圾收集,并且它们之间存在引用。这应该没问题,因为默认情况下finalize()什么都不做。如果您为两个对象编写自定义finalize()方法,则应该独立于它们最终确定的顺序编写代码。您还应该注意引用变为null,因为相应的对象可能已经被垃圾收集了。

是否有更好的替代方法来覆盖 finalize() 来检测和/或处理资源泄漏?

我认为使用终结器时最重要的事情是检测和记录/警告未处理的泄漏。记录此泄漏的最佳时机是在资源对象被垃圾回收之前。所以终结器很适合这个。

我想强调一点:我不会使用终结器来处理忘记关闭资源的程序员,而是通知他需要修复他的代码。

在实现终结器时还有其他需要注意的陷阱吗?

具有终结器的对象似乎也存在性能损失。你应该做一个测试,看看它对你有什么好处。如果存在重要的性能损失,我会尝试想象一种机制,仅在开发时使用终结器来记录资源泄漏。

按照 Amir Raminfar 的建议,在使用终结器继承对象时要小心。

还有一件事:看一下 [ 的源代码,FileInputStream][1]或者[FileOutputStream][2]他们出于同样的原因使用终结器。

于 2012-09-17T15:51:18.927 回答