我一直在阅读很多关于finalize()的Java新手问题,并发现没有人真正清楚地表明finalize()是一种不可靠的清理资源的方法,这有点令人困惑。我看到有人评论说他们用它来清理连接,这真的很可怕,因为唯一接近于保证连接关闭的方法是最后实现try (catch)。

我没有学过CS,但我已经用Java专业编程近十年了,我从来没有见过有人在生产系统中实现finalize()。这并不意味着它没有用处,或者和我一起工作的人一直在做正确的事情。

所以我的问题是,实现finalize()有哪些用例不能通过语言中的另一个进程或语法更可靠地处理?

请提供具体的场景或您的经验,简单地重复Java教科书,或最终确定的预期用途是不够的,因为这不是这个问题的意图。


当前回答

一个简单的规则:永远不要使用终结器。

对象具有终结器(不管它执行什么代码)这一事实本身就足以导致相当大的垃圾收集开销。

摘自Brian Goetz的一篇文章:

Objects with finalizers (those that have a non-trivial finalize() method) have significant overhead compared to objects without finalizers, and should be used sparingly. Finalizeable objects are both slower to allocate and slower to collect. At allocation time, the JVM must register any finalizeable objects with the garbage collector, and (at least in the HotSpot JVM implementation) finalizeable objects must follow a slower allocation path than most other objects. Similarly, finalizeable objects are slower to collect, too. It takes at least two garbage collection cycles (in the best case) before a finalizeable object can be reclaimed, and the garbage collector has to do extra work to invoke the finalizer. The result is more time spent allocating and collecting objects and more pressure on the garbage collector, because the memory used by unreachable finalizeable objects is retained longer. Combine that with the fact that finalizers are not guaranteed to run in any predictable timeframe, or even at all, and you can see that there are relatively few situations for which finalization is the right tool to use.

其他回答

我在生产代码中使用finalize的唯一一次是执行一个检查,检查给定对象的资源是否已被清理,如果没有,则记录一个非常明显的消息。它实际上并没有尝试自己去做,如果做得不好,它就会大喊大叫。结果很有用。

资源(文件,套接字,流等)需要关闭一旦我们完成他们。它们通常有close()方法,我们通常在try-catch语句的finally部分调用该方法。有时finalize()也可以被少数开发人员使用,但在我看来,这不是一种合适的方式,因为不能保证finalize总是被调用。

在Java 7中,我们有try-with-resources语句,可以这样使用:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

在上面的例子中,try-with-resource将通过调用close()方法自动关闭资源BufferedReader。如果我们愿意,我们也可以在自己的类中实现Closeable,并以类似的方式使用它。在我看来,这似乎更整洁,更容易理解。

一个简单的规则:永远不要使用终结器。

对象具有终结器(不管它执行什么代码)这一事实本身就足以导致相当大的垃圾收集开销。

摘自Brian Goetz的一篇文章:

Objects with finalizers (those that have a non-trivial finalize() method) have significant overhead compared to objects without finalizers, and should be used sparingly. Finalizeable objects are both slower to allocate and slower to collect. At allocation time, the JVM must register any finalizeable objects with the garbage collector, and (at least in the HotSpot JVM implementation) finalizeable objects must follow a slower allocation path than most other objects. Similarly, finalizeable objects are slower to collect, too. It takes at least two garbage collection cycles (in the best case) before a finalizeable object can be reclaimed, and the garbage collector has to do extra work to invoke the finalizer. The result is more time spent allocating and collecting objects and more pressure on the garbage collector, because the memory used by unreachable finalizeable objects is retained longer. Combine that with the fact that finalizers are not guaranteed to run in any predictable timeframe, or even at all, and you can see that there are relatively few situations for which finalization is the right tool to use.

就我个人而言,除了在一种罕见的情况下,我几乎从不使用finalize():我创建了一个自定义泛型类型集合,并编写了一个自定义finalize()方法,它执行以下操作:

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

(CompleteObject是我做的一个接口,让你指定你已经实现了很少实现的对象方法,如#finalize(), #hashCode()和#clone()))

因此,使用姐妹方法# setdestructivelyfinalizer(布尔值),使用我的集合的程序可以(帮助)确保销毁对该集合的引用也会销毁对其内容的引用,并处理可能在无意中保持JVM活动的任何窗口。我也考虑过停止任何线程,但这打开了一个全新的蠕虫罐头。

为了强调以上答案中的一点:终结器将在单独的GC线程上执行。我听说过一个大型的Sun演示,开发人员在一些终结器中添加了一个小睡眠,并故意让一个原本花哨的3D演示崩溃。

最好避免,可能的例外是test-env诊断。

Eckel的《Thinking in Java》在这方面有一个很好的章节。