为什么 finalize 没有被调用?

14

我有几个关于Java垃圾回收器的问题。

Q1.据我所知,当对象超出作用域且JVM即将收集垃圾时,会调用finalize()方法。我认为finalize()方法是被垃圾回收器自动调用的,但在这种情况下似乎不起作用。为什么需要显式地调用finalize()方法?

public class MultipleConstruct {
    int x,y;    
    public MultipleConstruct(int x)
    {
        this.x= x;
        y=5;        
        System.out.println("ONE");
    }

    @Override
    protected void finalize() throws Throwable {
        // TODO Auto-generated method stub
        super.finalize();
        System.out.println("FINALIZED");
    }
    public static void main(String[] args) throws Throwable {
        MultipleConstruct construct = new MultipleConstruct(3);
    }
}

Q2. 另外,垃圾回收器何时被调用?我了解gc是一个守护线程,并且根据剩余的堆大小由JVM调用。那是否意味着JVM等待程序使用资源的阈值限制,然后通知gc清除垃圾对象。

EDIT: gc如何解决循环引用?


每当JVM认为需要调用GC时,它就会执行GC,尽管我记得Oracle JVM实际上会使用System.gc()来调用它。我记得也不能保证它被调用。对于您的示例,您是如何测试是否/何时调用它的? - Dave Newton
不要使用finalize(),它与C++的析构函数没有任何等价性。 - DwB
@DwB 我同意,但是有什么方法可以在Java中实现类似析构函数的行为,即无错资源清理呢? - prap19
使用try {} finally {}块进行无错误的资源清理。 - DwB
@DWB,这并不是解决方案,例如您在一个对象中有绘制图像的方法,该对象已经超出范围,但图像仍然显示在屏幕上。您需要一个析构函数来清除屏幕上的图像。不是吗? - prap19
无法保证你的终结器会在何时、是否被调用。 - Thorbjørn Ravn Andersen
4个回答

13

finalize()方法有很多需要实现的细节,简单来说:

当对象在其 finalize 方法(如果有)运行后仍然无法访问时,它处于 finalized 状态。已经 finalization的对象正在等待被释放。请注意,虚拟机实现控制何时运行终结器。与依赖于 finalizer 相比,您几乎总是更好地进行自己的清除。使用 finalize 还可能会留下关键资源,这些资源将在不确定的时间内无法恢复。

在您的情况下,它不打印的原因是您不知道 finalizer 线程何时调用 finalize() 方法。发生的情况是程序在任何内容被打印之前终止了。要检查它:

编辑 main 代码中的代码(注意:这不能保证也不应该依赖它,但它确实会有时候打印一些信息)

for(int i =0;i<1000000;i++)
    {
        MultipleConstruct construct = new MultipleConstruct(3);
        construct = null;
    }

使用finalize()存在很多缺点,比如会增加对象构造的时间、可能会导致内存泄漏和内存耗尽等。如果在finalize()中强烈引用同一对象,则第二次不会调用它,从而可能使系统处于不良状态等等......

唯一应该使用finalize()的地方是作为安全网来处理任何资源的关闭,例如InputStream使用它进行关闭(但不能保证在程序仍然活动时它会被运行)。另一个使用它的地方是在使用本机代码时,垃圾回收器无法控制。

更多信息请访问:

http://jatinpuri.com/?p=106


5

q1) finalize方法在对象被垃圾回收时调用,因此,如果没有进行GC,您的终结器可能不会被调用。您需要简单地调用super以保留Object实现提供的行为。

q2) GC执行的确切时机取决于许多因素,例如:您使用的JVM、调整参数、可用堆内存量等。因此,它不仅依赖于已使用堆阈值。您还可以通过System.gc()请求执行GC,但是不能保证何时执行。

您可以在http://java.sun.com/performance/reference/whitepapers/tuning.html中找到有关如何配置GC的详细信息。


3

该方法可能最终被调用,也可能根本没有被调用

基本上,垃圾回收器会扫描堆中所有不可达的东西,并在这些东西上运行终结器(在此之后,需要再次证明它是不可达的,以便释放它)

然而,GC 可能需要一些时间(实际上具体取决于程序行为,无法确定),才能找到这些东西,这就是为什么你不应该依赖它来处理关键数据。

编辑:至于循环引用,它区分具有 finalize 方法和没有 finalize 方法的对象

为了使对象被释放(从主内存中删除),它不能被任何代码访问(包括尚需运行的终结器)

当 2 个具有终结器的对象有资格运行终结器时,GC 会随机选择一个对象并对其运行终结器,然后可以运行另一个对象的终结器

请注意,终结器可以在对象的字段已经完成终结或尚未完成终结的情况下运行


1
那么您的意思是添加finalize()方法并不能确保无漏洞清理?例如,在这种情况下,如果有一个文件对象处于打开状态,并且我想在持有该文件对象的对象死亡之前清理它,则除非显式调用,否则finalize()方法无用。 - prap19
2
@prap19 是的,这就是为什么close()finalize()更可靠的原因。 - ratchet freak
1
对象中的close()函数?这是您的意思吗?就文件而言,那只是一个例子。我关心的是JAVA中类似析构函数的行为。 - prap19
@prap19 我指的是 Closeable 接口的 close() 方法,很多时候 finalize() 是完全无用的,或者有一种显式释放资源的解决方案。 - ratchet freak
从Java 7开始,有可能使用自动资源管理(使用try),它将在try块的末尾自动调用close()。这将简化对象的处理。您不应该使用finalize来处理资源。 - raymi
循环引用并没有特殊处理。这句话是误导性的。像HotSpot这样的JVM会针对具有微不足道finalize()方法对象优化处理过程,但这与引用图形的形状无关。此外,如果多个对象等待被定期处理,则它们可能按任意顺序甚至在多个终结线程中同时处理。 - Holger

-3

finalize()方法会在垃圾回收时自动调用。System.gc()方法会强制调用垃圾回收器,但我们需要在此之前销毁对象。 例如:

     public class Sample 
{
    public Sample()
    {
        System.out.println("Object created");
    }
    @Override
    public void finalize()
    {
        System.out.println("Object Destroyed");
    }
    public static void main(String args[])
    {
        Sample x=new Sample();
        Sample y=new Sample();

        x=null;
        y=null;

        System.gc();
    }
}

System.gc() 无法强制JVM运行垃圾收集器。它只是通知JVM对象可以进行回收。 - prap19
4
System.gc()并不会强制进行垃圾回收。它表示希望进行垃圾回收,但JVM只有在自己想要运行垃圾回收时才会运行它。 - DwB
1
重点在于,为了调用finalize方法(如上面的代码所示),对象必须被设置为“null”或者被销毁。否则,当垃圾回收发生时,JVM将会自动调用finalize方法。 - anurag11
1
即使System.gc()触发了垃圾回收,也不能保证看到finalize()方法的效果(事实上,在这个例子中相当不可能),因为finalization是在垃圾收集器将对象排队之后异步发生的,而在这里,JVM在垃圾回收后立即退出。 - Holger
它取决于JVM的设置方式。-XX:DisableExplicitGC JVM标志可用于配置显式System.gc()是否会产生任何影响。 - shubhamgarg1

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