389

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

我没有受过 CS 教育,但我已经专业地使用 Java 编程近十年了,而且我从未见过有人finalize()在生产系统中实现过。这仍然不意味着它没有它的用​​途,或者与我一起工作的人一直在做正确的事情。

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

请提供具体的场景或您的经验,只是重复 Java 教科书,或者 finalize 的预期用途是不够的,因为这不是这个问题的意图。

4

21 回答 21

242

您可以将其用作持有外部资源(套接字、文件等)的对象的支持。实现close()需要调用的方法和文档。

如果您检测到它尚未完成,请实施finalize()以执行处理。close()也许有一些东西被倾倒来stderr指出你正在清理一个有缺陷的来电者。

它在异常/错误的情况下提供额外的安全性。不是每个调用者每次都会做正确的try {} finally {}事情。不幸的是,但在大多数环境中都是如此。

我同意它很少需要。正如评论者所指出的,它带有 GC 开销。只有在长时间运行的应用程序中需要“腰带和吊带”安全性时才使用。

我看到从 Java 9 开始,Object.finalize()它已被弃用!他们将我们指向java.lang.ref.Cleanerjava.lang.ref.PhantomReference作为替代方案。

于 2008-10-01T15:27:50.363 回答
183

finalize()是对 JVM 的提示,在未指定的时间执行代码可能会很好。当您希望代码神秘地无法运行时,这很好。

在终结器中做任何重要的事情(基本上除了日志之外的任何事情)在三种情况下也很好:

  • 您想赌其他最终确定的对象仍将处于您程序的其余部分认为有效的状态。
  • 您想向所有具有终结器的类的所有方法添加大量检查代码,以确保它们在终结后行为正确。
  • 您想不小心复活已完成的对象,并花费大量时间试图弄清楚它们为什么不起作用,和/或为什么它们最终发布时没有完成。

如果你认为你需要 finalize(),有时你真正想要的是一个幻像引用(在给出的示例中,它可能持有对其引用对象使用的连接的硬引用,并在幻像引用排队后关闭它)。这也具有它可能神秘地永远不会运行的属性,但至少它不能调用方法或恢复最终对象。因此,它适用于您并非绝对需要干净地关闭该连接,但您非常愿意,并且您班级的客户不能或不会自己调用 close 的情况(这实际上很公平 -什么' 如果您设计的接口需要在收集之前采取特定操作,那么拥有垃圾收集器的意义何在?这让我们回到了 malloc/free 的时代。)

其他时候,您需要您认为自己正在管理的资源更加强大。例如,为什么需要关闭该连接?它最终必须基于系统提供的某种I/O(套接字、文件等),那么当最低级别的资源被gced时,为什么不能依靠系统为您关闭它呢?如果另一端的服务器绝对要求您干净地关闭连接,而不是仅仅丢弃套接字,那么当有人绊倒您的代码正在运行的机器的电源线或中间网络中断时会发生什么?

免责声明:我过去曾研究过 JVM 实现。我讨厌终结者。

于 2008-10-01T16:05:23.067 回答
56

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

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

来自Brian Goetz 的一篇文章

与没有终结器的对象相比,具有终结器的对象(具有非平凡 finalize() 方法的对象)具有显着的开销,应谨慎使用。可终结的对象分配和收集都比较慢。在分配时,JVM 必须向垃圾收集器注册任何可终结对象,并且(至少在 HotSpot JVM 实现中)可终结对象必须遵循比大多数其他对象更慢的分配路径。同样,可终结对象的收集速度也较慢。至少需要两个垃圾收集周期(在最好的情况下)才能回收可终结对象,并且垃圾收集器必须做额外的工作来调用终结器。结果是分配和收集对象花费了更多时间,垃圾收集器承受了更大的压力,因为无法访问的可终结对象使用的内存保留的时间更长。再加上终结器不能保证在任何可预测的时间范围内运行,甚至根本不能保证这一事实,您可以看到在相对较少的情况下终结器是正确的工具。

于 2008-10-01T17:38:32.093 回答
50

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

于 2008-10-01T15:44:18.797 回答
38

自 1998 年以来,我一直在专业地做 Java,但我从未实现过finalize(). 不止一次。

于 2008-10-01T15:24:23.553 回答
30

公认的答案很好,我只是想补充一点,现在有一种方法可以在不实际使用它的情况下拥有 finalize 的功能。

查看“参考”类。弱参考、幻影参考和软参考。

您可以使用它们来保留对所有对象的引用,但此引用 ALONE 不会停止 GC。这样做的好处是你可以让它在它被删除时调用一个方法,并且这个方法可以保证被调用。

至于 finalize:我曾经使用 finalize 来了解哪些对象正在被释放。你可以用静态、引用计数等玩一些巧妙的游戏——但这只是为了分析,但要注意这样的代码(不仅仅是在 finalize 中,而是你最有可能看到它的地方):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

这表明有人不知道他们在做什么。几乎不需要像这样的“清理”。当类被 GC 时,这是自动完成的。

如果您在 finalize 中找到类似的代码,那么可以保证编写它的人会感到困惑。

如果它在其他地方,则可能是代码是对坏模型的有效补丁(一个类会存在很长时间,由于某种原因,它引用的东西必须在对象被 GC 之前手动释放)。通常是因为有人忘记删除侦听器或其他东西,并且无法弄清楚为什么他们的对象没有被 GC'd,所以他们只是删除了它所指的东西,然后耸耸肩走开了。

它不应该被用来“更快”地清理东西。

于 2008-10-01T16:56:03.303 回答
28

我不确定你能做些什么,但是......

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

所以我猜太阳发现了一些(他们认为)应该使用它的案例。

于 2008-10-05T14:42:56.983 回答
20
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

=====================================

结果:

This is finalize

MyObject still alive!

======================================

因此,您可以在 finalize 方法中使无法访问的实例可访问。

于 2009-06-02T01:55:24.967 回答
11

finalize()可用于捕获资源泄漏。如果资源应该关闭但没有将它没有关闭的事实写入日志文件并关闭它。这样您就可以消除资源泄漏并给自己一种方法来知道它已经发生,以便您可以修复它。

自 1.0 alpha 3 (1995) 以来,我一直在使用 Java 进行编程,但我还没有为任何事情重写 finalize...

于 2009-02-13T18:09:38.163 回答
6

您不应该依赖 finalize() 来为您清理资源。finalize() 在类被垃圾回收之前不会运行,如果那样的话。使用完资源后,最好显式释放资源。

于 2008-10-01T15:27:49.520 回答
5

小心你在finalize(). 特别是如果您将它用于调用 close() 以确保资源被清理。我们遇到了几种情况,我们将 JNI 库链接到正在运行的 java 代码中,并且在我们使用 finalize() 调用 JNI 方法的任何情况下,我们都会遇到非常严重的 java 堆损坏。损坏不是由底层 JNI 代码本身引起的,本机库中的所有内存跟踪都很好。这只是我们从 finalize() 调用 JNI 方法的事实。

这是使用仍在广泛使用的 JDK 1.5。

直到很久以后我们才发现出了什么问题,但最终罪魁祸首始终是使用 JNI 调用的 finalize() 方法。

于 2008-10-01T16:00:22.017 回答
5

为了突出上述答案中的一点:终结器将在单独的 GC 线程上执行。我听说过一个主要的 Sun 演示,开发人员在其中为一些终结器添加了一个小睡眠,并故意让一个原本花哨的 3D 演示屈服。

最好避免,除了测试环境诊断。

Eckel 的 Thinking in Java 对此有很好的部分

于 2008-10-01T17:43:12.083 回答
4

嗯,我曾经用它来清理没有返回到现有池的对象。

他们经常被转来转去,所以不知道什么时候可以安全地回到游泳池。问题在于它在垃圾收集期间引入了巨大的惩罚,这远远超过了池化对象所节省的任何成本。在我拆掉整个游泳池之前,它已经投入生产了大约一个月,让所有东西都充满活力并完成了它。

于 2008-10-01T16:15:39.313 回答
3

在编写将由其他开发人员使用的代码时,需要调用某种“清理”方法来释放资源。有时,其他开发人员忘记调用您的清理(或关闭、销毁或其他)方法。为避免可能的资源泄漏,您可以检查 finalize 方法以确保该方法被调用,如果不是,您可以自己调用它。

许多数据库驱动程序在他们的 Statement 和 Connection 实现中这样做,以便为忘记调用它们的开发人员提供一点安全性。

于 2008-10-01T15:25:07.407 回答
2

编辑:好的,它真的不起作用。我实现了它,并认为如果它有时失败,那对我来说没问题,但它甚至没有一次调用 finalize 方法。

我不是专业的程序员,但在我的程序中,我有一个案例,我认为它是使用 finalize() 的一个很好的例子,它是一个缓存,可以在其内容被销毁之前将其写入磁盘。因为不需要每次销毁时都执行它,它只会加快我的程序,我希望我没有做错。

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}
于 2010-02-03T12:10:59.377 回答
2

删除已添加到全局/静态位置(不需要)并且需要在删除对象时删除的东西会很方便。例如:

    私人无效 addGlobalClickListener() {
        weakAwtEventListener = new WeakAWTEventListener(this);

        Toolkit.getDefaultToolkit().addAWTEventListener(weakAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @覆盖
    protected void finalize() 抛出 Throwable {
        super.finalize();

        如果(弱AwtEventListener!= null){
            Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener);
        }
    }
于 2011-04-07T08:11:40.953 回答
1

接受的答案列表可以完成在 finalize 期间关闭资源。

然而,这个答案表明,至少在使用 JIT 编译器的 java8 中,您会遇到意想不到的问题,有时甚至在您完成从对象维护的流中读取之前调用终结器。

因此,即使在这种情况下,也不建议调用 finalize。

于 2014-12-03T12:56:26.890 回答
0

iirc - 您可以使用 finalize 方法来实现昂贵资源的池化机制 - 所以它们也不会获得 GC。

于 2013-09-19T17:01:58.387 回答
0

就个人而言,我几乎从不使用,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是我制作的一个接口,可让您指定您已经实现了很少实现的Object方法,例如#finalize()#hashCode()#clone()

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

于 2015-03-16T16:36:52.393 回答
0

一旦我们完成了资源(文件、套接字、流等),就需要关闭它们。它们通常具有close()我们通常在语句finally部分中调用的方法。try-catch有时finalize()也可以由少数开发人员使用,但 IMO 这不是一种合适的方式,因为不能保证总是会调用 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 将BufferedReader通过调用close()方法自动关闭资源。如果我们愿意,我们也可以在我们自己的类中实现Closeable并以类似的方式使用它。IMO 似乎更简洁易懂。

于 2015-06-24T03:54:26.150 回答
0

作为旁注:

覆盖 finalize() 的对象由垃圾收集器特殊处理。通常,在对象不再在范围内之后,对象会在回收周期中立即销毁。但是,可终结的对象被移动到队列中,其中单独的终结线程将耗尽队列并在每个对象上运行 finalize() 方法。一旦 finalize() 方法终止,对象将最终准备好在下一个周期进行垃圾回收。

资料来源:java-9 上不推荐使用 finalize()

于 2018-06-04T07:20:37.610 回答