你为什么要实现finalize()?

时间:2008-10-01 15:22:10

标签: java jvm

我一直在阅读finalize()上的许多新手Java问题,并且发现没有人真正明白终结()是一种清理资源的不可靠方式,这让人感到很困惑。我看到有人评论说他们用它来清理Connections,这真的很可怕,因为接近Connection的关闭的唯一方法就是最终实现try(catch)。

我没有接受过CS培训,但是我已经用Java专业编程了近十年了,我从来没有见过有人在生产系统中实现finalize()。这仍然不意味着它没有它的用​​途,或者我与之合作过的人一直在做正确的事。

所以我的问题是,实现finalize()的用例是什么,无法通过语言中的其他进程或语法更可靠地处理?

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

21 个答案:

答案 0 :(得分:216)

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

如果检测到尚未完成,请执行finalize()进行close()处理。也许有一些倾销到stderr的东西,指出你在一个错误的来电者之后正在清理。

它在特殊/错误的情况下提供额外的安全性。并非每个调用者都会每次都执行正确的try {} finally {}内容。不幸的是,但在大多数环境中都是如此。

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

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

答案 1 :(得分:163)

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

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

  • 你想赌博,其他最终确定的对象仍将处于你的程序其余部分认为有效的状态。
  • 您希望为具有终结器的所有类的所有方法添加大量检查代码,以确保它们在最终确定后正确运行。
  • 你想要意外地复活最终的物品,并花费大量的时间来弄清楚它们为什么不起作用,和/或为什么它们最终在最终被释放时不能最终确定。

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

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

免责声明:我过去曾参与过JVM实施。我讨厌终结者。

答案 2 :(得分:54)

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

一个对象具有终结器(无论它执行什么代码)的事实足以导致垃圾收集的相当大的开销。

来自Brian Goetz的article

  

带终结器的对象(那些   有一个非平凡的finalize()方法)   与...相比,有很大的开销   没有终结者的对象,应该   谨慎使用。 Finalizeable   对象的分配速度较慢   收集速度较慢。分配时   时间,JVM必须注册任何   带垃圾的可终结对象   收藏家,(至少在   HotSpot JVM实现)   可完成的对象必须遵循   分配路径比大多数其他路径慢   对象。同样,可以完成   对象的收集速度也较慢。它   至少需要两次垃圾回收   在a之前的周期(在最好的情况下)   可以回收的可终结对象,   而垃圾收集器必须这样做   额外的工作来调用终结器。   结果是花费更多时间   分配和收集对象和   垃圾压力更大   收集器,因为使用的内存   无法达到的可终结对象是   保留更久。结合它   事实上,终结者不是   保证在任何可预测的情况下运行   时间表,甚至根本没有,你可以   看到的相对较少   最终确定的情况   正确的工具。

答案 3 :(得分:43)

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

答案 4 :(得分:32)

自1998年以来,我一直从事Java专业工作,而且我从未实现finalize()。不是一次。

答案 5 :(得分:26)

接受的答案是好的,我只是想补充一点,现在有一种方法可以使用finalize的功能,而根本不使用它。

查看“参考”类。弱参考,幻影参考&软参考。

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

至于完成: 我使用finalize一次来了解哪些对象被释放。你可以用静态,引用计数等来玩一些整洁的游戏 - 但它只是用于分析,但要注意这样的代码(不仅仅是最终确定,而是你最有可能看到的地方):

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

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

如果你在最终确定中找到这样的代码,那么保证编写它的人很困惑。

如果它在其他地方,可能是代码是坏模型的有效补丁(一个类长时间停留,并且出于某种原因,它必须在对象被GC之前手动释放它所引用的东西) 。一般来说,这是因为有人忘了删除一个听众或其他东西而无法弄清楚为什么他们的对象不是GC,所以他们只是删除它引用的东西,耸耸肩膀走开。

永远不应该用它来清理“更快”。

答案 6 :(得分:25)

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

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

所以我猜太阳发现一些的情况,他们认为应该使用它。

答案 7 :(得分:21)

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方法中使无法访问的实例可达。

答案 8 :(得分:9)

finalize()可用于捕获资源泄漏。如果资源应该被关闭但是没有写入它没有关闭到日志文件并关闭它的事实。这样你就可以消除资源泄漏,并让自己知道它已经发生了,所以你可以解决它。

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

答案 9 :(得分:6)

您不应该依赖finalize()来为您清理资源。如果那么垃圾收集,则finalize()将不会运行。使用它们时显式释放资源要好得多。

答案 10 :(得分:5)

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

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

Eckel在Java中的思考有a good section

答案 11 :(得分:3)

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

许多数据库驱动程序在其Statement和Connection实现中执行此操作,以便为忘记调用它们的开发人员提供一点安全性。

答案 12 :(得分:3)

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

他们被传递了很多,因此无法确定何时可以安全地返回游泳池。问题是它在垃圾收集过程中引入了一个巨大的惩罚,远远超过汇集对象的任何节省。它在生产了大约一个月之后才撕掉了整个游泳池,让一切变得充满活力并完成了它。

答案 13 :(得分:3)

请注意finalize()中的操作。特别是如果您正在使用它来调用close()以确保清理资源。我们遇到了几种情况,我们将JNI库链接到正在运行的java代码,在任何使用finalize()来调用JNI方法的情况下,我们都会遇到非常糟糕的Java堆损坏。腐败不是由底层JNI代码本身引起的,所有内存跟踪在本机库中都很好。事实上,我们正在从finalize()中调用JNI方法。

这是JDK 1.5,它仍然被广泛使用。

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

答案 14 :(得分: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);
}

答案 15 :(得分:2)

删除已添加到全局/静态位置(不需要)的内容非常方便,并且在删除对象时需要将其删除。例如:

    private void addGlobalClickListener() {
        weakAwtEventListener = new WeakAWTEventListener(this);

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

    @Override
    protected void finalize() throws Throwable {
        super.finalize();

        if(weakAwtEventListener != null) {
            Toolkit.getDefaultToolkit().removeAWTEventListener(weakAwtEventListener);
        }
    }

答案 16 :(得分:0)

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

答案 17 :(得分:0)

接受的答案列出了在完成期间关闭资源的过程。

但是this answer表明,至少在使用JIT编译器的java8中,您会遇到意外问题,即使在您从对象维护的流中读取之前,有时甚至会调用终结器。

所以即使在那种情况下,调用finalize也会建议

答案 18 :(得分: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()和{ {1}})功能

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

答案 19 :(得分:0)

完成后,资源(文件,套接字,流等)需要关闭。它们通常采用finally方法,我们通常在try-catch语句的finalize()部分中调用。有时候,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. } 也可以由少数开发人员使用,但IMO不是合适的方式,因为无法保证将始终调用finalize。

在Java 7中,我们得到了try-with-resources语句,可以像:

一样使用
BufferedReader

在上面的示例中,try-with-resource将通过调用close()方法自动关闭资源EditText。如果我们想要,我们也可以在我们自己的类中实现Closeable并以类似的方式使用它。 IMO似乎更简洁易懂。

答案 20 :(得分:0)

作为旁注:

  

重写finalize()的对象由垃圾收集器专门处理。通常,在对象不再在范围之后,在收集周期期间立即销毁对象。但是,最终化的对象将被移动到队列中,其中单独的终结线程将耗尽队列并对每个对象运行finalize()方法。一旦finalize()方法终止,该对象将在下一个循环中为垃圾收集做好准备。

来源:finalize() deprecated on java-9