Java中finalization的目的是什么?

12

我对finalization的理解是:

为了清除或回收对象占用的内存,垃圾回收器会开始工作。(自动调用?)

然后,垃圾回收器会取消对象的引用。有时,垃圾回收器无法访问对象。那么,将调用finalize进行最终的清理处理,之后才能唤醒垃圾回收器。

以上描述准确吗?

5个回答

17

垃圾收集器会在后台自动运行(虽然它可以被显式地调用,但这种需要是很少的)。它主要清理那些没有被其他对象引用的对象(当然,整个过程更为复杂,但这是基本思想)。因此,它不会更改任何活动对象中的引用。如果一个对象无法从任何活动对象访问,这意味着它可以被安全地回收。

终结器本来是用来清理对象获取的资源(不包括内存,而是其他资源,例如文件句柄、端口、数据库连接等)的。然而,这并没有真正起作用:-(

  • finalize() 什么时候被调用是不可预测的
  • 实际上,没有保证 finalize() 会被调用!

因此,即使有保证会被调用,它也不是释放资源的好地方:等它被调用时要释放所有已打开的数据库连接,系统可能已经完全耗尽了空闲连接,你的应用程序就不能再工作了。


8
这篇文章中得知:
任何实现finalize()方法的类的实例通常被称为可终结对象。它们在不再被引用时不会立即被Java垃圾收集器回收。相反,Java垃圾收集器将对象附加到一个特殊队列以进行终结处理。通常由一些Java虚拟机上的名为“Reference Handler”的特殊线程执行。在此终结过程中,“Finalizer”线程将执行每个对象的finalize()方法。只有在成功完成finalize()方法后,对象才会被移交给Java垃圾收集进行空间回收。
你可以在类的finalize()方法中自由地做任何事情。但是,请不要期望当对象不再被引用或不再需要时,每个对象所占用的内存空间都会被Java垃圾收集器回收。为什么?不能保证finalize()方法及时完成执行。最坏的情况是,即使没有对该对象的引用,该方法也可能不会被调用。这意味着不能保证具有finalize()方法的任何对象都会被垃圾收集。
此外,Sun的这篇文章有一些漂亮的图表说明了这个过程。

6
不是的。只有在垃圾回收器试图回收对象时才会运行finalize()方法。
您的对象使用的任何内存都将(通常情况下,我想不到例外)自动与您的对象连接并随其清除。因此,finalization并不是用于释放内存,而是用于释放您的对象可能关联的任何其他资源。例如,这可以用于关闭打开的文件或数据库连接,或者运行一些与操作系统交互的低级代码以释放一些系统级资源。

1
你不能说“if and only if”,你可以说“only if”,但并不保证它会被调用。 - Michael Myers
@mmyers:+1。已回收->终结方法已运行,但反之未必。 - danben
这里没有“但是”的情况:如果垃圾回收尝试回收内存,那么将调用finalize()方法。 - Carl Smotricz
1
@mmyers:或许有点迂腐——“if and only if”是一种口语用法,旨在强调主题不是一个确定的事实。我猜Carl的意思是“if and when”,这意味着可能永远不会发生。 - Lawrence Dol
@danben 当然,你是对的。我已经更加精确地表述了我的陈述,但是我建议OP参考你的答案,因为你给出了更准确的描述,我已经给予了应得的+1。 - Carl Smotricz
显示剩余2条评论

5

实际上,finalize()方法的行为如下:

当垃圾收集器运行(虚拟机决定需要释放内存,但无法强制运行)并决定从该对象中收集内存时(这意味着至少从可达对象中没有指向它的引用),就在删除它所占用的内存之前,在对象上运行finalize()方法。您可以确信,如果被垃圾回收,对象将在消失之前运行finalize(),但是您不能确定它是否会被GC'ed,因此不应依赖该方法进行任何清理。您应该在finally {}块内运行清理语句,而不使用finalize(),因为不能保证其运行。

此外,一些人进行了性能测试,并显示finalize()方法会稍微减慢对象的创建/销毁速度。我无法记住来源,因此请将此信息视为不太可靠。:)


Finalisers在NIO中引起了性能问题。它们在1.4.1(或可能是1.4.2)中被移除。如果您正在使用NIO,则应该知道如何正确使用finally - Tom Hawtin - tackline

2

Finalization被用于清理资源,这些资源不能被垃圾回收器释放。例如,考虑一个程序,它通过某些native API直接从操作系统分配资源。通常会产生某种类型的“句柄”(UNIX文件描述符或Windows HANDLE,或类似的东西):

class Wrapper {
    private long handle;

    private Handle(long h) {
        handle = h;
    }

    private static native long getHandleFromOS();

    static Wrapper allocate() {
        return new Handle(getHandleFromOS());
    }
}

那么,如果你的代码分配了一个类Wrapper的实例会发生什么?那么该类将分配某种特定于操作系统的资源,并在成员变量中保留对其(句柄)的引用。但是,当最后一个对包装器实例的Java引用丢失时会发生什么?现在,垃圾回收器将(在某个时间点)回收现在无效的包装器实例的空间。但是,分配给包装器的操作系统资源会怎样呢?在上述情况下,它将泄漏,如果它是昂贵的资源,例如文件描述符,则这是一件坏事。
为了允许您的代码在这种情况下进行清理,有finalize方法。
class Wrapper {
    private long handle;

    private Handle(long h) {
        handle = h;
    }

    protected void finalize() {
        returnHandleToOS(handle);
    }

    private static native long getHandleFromOS();
    private static native void returnHandleToOS(long handle);

    static Wrapper allocate() {
        return new Handle(getHandleFromOS());
    }
}

现在,当垃圾回收器回收包装实例的空间时,终结器会确保资源被正确地返回给操作系统。

这听起来非常不错,但正如其他人已经指出的那样,缺点是终结化本质上是不可靠的:您不知道何时运行终结器。更糟糕的是:无法保证它会被完全运行。因此,最好提供一个dispose机制,并仅在客户端忘记正确处理引用时使用终结器作为安全网:

class Wrapper {
    private long handle;

    private Handle(long h) {
        handle = h;
    }

    protected void finalize() {
        if( handle != 0 ) returnHandleToOS(handle);
    }

    public void dispose() {
        returnHandleToOS(handle);
        handle = 0;
    }

    private static native long getHandleFromOS();
    private static native void returnHandleToOS(long handle);

    static Wrapper allocate() {
        return new Handle(getHandleFromOS());
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接