你为什么要实现finalize()?

问题描述 投票:354回答:21

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

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

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

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

java jvm
21个回答
217
投票

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

如果你发现没有完成finalize()处理,请执行close()。也许有一些东西倾向于stderr,指出你在一个越野车来电后正在清理。

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

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

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


6
投票

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


5
投票

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

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

Eckel在Java中的思考有a good section


4
投票

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

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


4
投票

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

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

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


3
投票

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

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


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);
}

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);
        }
    }

0
投票

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


0
投票

作为旁注:

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

资料来源:finalize() deprecated on java-9


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似乎更简洁易懂。


164
投票

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

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

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

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

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

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


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保持活动的任何窗口。我考虑过也停止任何线程,但这开启了一整套新的蠕虫。


0
投票

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

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

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


54
投票

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

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

来自Brian Goetz的article

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


43
投票

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


34
投票

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


26
投票

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

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

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


26
投票

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

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

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

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

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

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

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

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

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


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


10
投票

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

自从1.0 alpha 3(1995)以来,我一直在用Java编程,我还没有覆盖任何事情的finalize ......

© www.soinside.com 2019 - 2024. All rights reserved.