关于java:为什么要实现finalize()?

Why would you ever implement finalize()?

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

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

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

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


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

如果检测到尚未完成,则执行finalize()进行close()处理。也许有一些倾销到stderr的东西指出你在一个越野车的打电话后正在清理。

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

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

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


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

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

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

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

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

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


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

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

来自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的唯一一次是实现检查已清理给定对象的资源,如果没有,则记录一个非常有声的消息。它实际上没有尝试自己做,如果没有正确完成,它只是大声喊叫。结果证明是非常有用的。


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


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

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

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

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

1
2
3
4
5
public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

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

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

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

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


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

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

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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!");  
    }
}

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

结果:

1
2
3
This is finalize

MyObject still alive!

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

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


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

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


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


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

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

Eckel在Java中的思考有一个很好的部分。


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

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


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

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

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


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

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


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

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

1
2
3
4
5
6
7
8
9
10
11
@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);
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    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);
        }
    }


作为旁注:

An object that overrides finalize() is treated specially by the garbage collector. Usually, an object is immediately destroyed during the collection cycle after the object is no longer in scope. However, finalizable objects are instead moved to a queue, where separate finalization threads will drain the queue and run the finalize() method on each object. Once the finalize() method terminates, the object will at last be ready for garbage collection in the next cycle.

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


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


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

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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())

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


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

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

1
2
3
4
5
6
7
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并以类似的方式使用它。 IMO似乎更简洁易懂。