Java中的finalize()方法是何时被调用的?

367

我需要知道在JVM中何时调用finalize()方法。我创建了一个测试类,通过覆盖finalize()方法将其写入文件,但它未被执行。有人可以告诉我为什么它没有被执行吗?


40
顺便提一下:在Java 9中,finalize被标记为已弃用。 - Reg
2
请看:https://www.infoq.com/news/2017/03/Java-Finalize-Deprecated - wleao
如果任何东西都有对您的对象甚至类的引用,那么finalize()和垃圾回收并没有任何影响。 - ha9u63a7
1
从Java9开始,它已被弃用。https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#finalize-- - srk
截至https://openjdk.java.net/jeps/421(Java 18),终结器尚未被删除。 - Thorbjørn Ravn Andersen
18个回答

2

finalize 方法不能保证一定会被调用。该方法在对象变得可回收时被调用。有许多情况下,对象可能不会被垃圾回收。


4
不正确。你所说的是当一个对象变得不可达时,它才会被finalize,但实际上是在方法被收集时才会调用finalize。 - Stephen C

2
如果一个对象不被任何活动线程或其他静态引用所引用,那么它就会成为“垃圾回收”或GC的候选对象。换句话说,如果这个对象的所有引用都是null,那么它就可以被垃圾回收。循环依赖不被视为引用,因此,如果对象A有对象B的引用,而对象B又有对象A的引用,并且它们没有任何其他存活的引用,那么对象A和B都将成为垃圾回收的候选对象。
通常,在以下情况下,Java中的对象将变得可以进行垃圾回收:
  1. 该对象的所有引用均明确设置为null,例如:object = null
  2. 对象创建在块内部,并且一旦控制退出该块,引用就会超出作用域。
  3. 父对象设置为null,如果一个对象持有另一个对象的引用,当你将容器对象的引用设置为null时,子或包含的对象将自动成为可回收的。
  4. 如果一个对象只有通过WeakHashMap存活的引用,则它将成为可回收的。

@Holger:在JIT能够看到对象从创建到丢弃期间发生的所有事情的情况下,我不认为JIT有任何理由提前触发终结器。真正的问题是代码需要做什么来确保终结器不能在特定方法内触发。在.NET中,有一个GC.KeepAlive()函数,它除了强制GC假定可能使用对象外什么也不做,但我不知道Java中是否有这样的函数。可以使用volatile变量来实现这个目的,但仅出于这个目的使用变量似乎是浪费的。 - supercat
@supercat:JIT不会触发finalization,它只是安排代码以不保留引用,但直接将FinalizerReference加入队列可能更有益,这样就不需要GC循环来发现没有引用。同步足以确保* happens-before *关系;由于finalization可能(实际上是)在不同的线程中运行,因此通常形式上仍然是必要的。Java 9将添加Reference.reachabilityFence - Holger
@Holger:如果JIT优化掉了对象的创建,那么只有在JIT直接生成调用的情况下才会调用Finalize。对于只希望在单个线程中使用的对象,是否通常期望同步代码?如果一个对象执行一些需要在放弃之前撤消的操作(例如打开套接字连接并获取对端资源的独占使用权),那么在代码仍在使用套接字时,使用终结器关闭连接将是一场灾难。代码是否使用同步是正常的... - supercat
仅为了防止终结器过早关闭连接,是吗? - supercat
@supercat:由于终结器在任意线程中运行,因此在使用finalize()时使用线程安全的构造是强制性的。但是当您仅依靠不可变性来实现线程安全时,缺乏显式对象使用确实可能是灾难性的。这是一个真正的问题。这就是为什么使用引用API更可取的原因;在那里,您可以完全控制何时以及在哪个线程中轮询ReferenceQueue以清理资源。 - Holger
显示剩余2条评论

2

JDK 18的最新消息

根据在openjdk 18上发布的JEPS 421finalize()方法的功能将被标记为deprecated(forRemoval=true),这意味着在jdk 18之后的某个版本中将永久删除该方法。

从jdk 18开始,新的命令行选项--finalization=disabled将在所有地方禁用终结机制,甚至包括jdk本身内部的声明。

这也与此处的问题相关,因为它计划删除的原因是它包含一些主要缺陷。其中一个缺陷是对象变得不可达和其终结器被调用之间可能会经过很长时间。同时,GC不能保证任何终结器都会被调用。


1

覆盖 finalize 方法的类

public class TestClass {    
    public TestClass() {
        System.out.println("constructor");
    }

    public void display() {
        System.out.println("display");
    }
    @Override
    public void finalize() {
        System.out.println("destructor");
    }
}

finalize方法被调用的几率

public class TestGarbageCollection {
    public static void main(String[] args) {
        while (true) {
            TestClass s = new TestClass();
            s.display();
            System.gc();
        }
    }
}

当内存被转储对象过载时,垃圾回收器将调用finalize方法。运行并查看控制台,在内存过载时不会经常发现调用finalize方法,但当内存过载时,将调用finalize方法。

1

finalize()在垃圾回收之前被调用。当对象超出范围时,它不会被调用。这意味着您无法知道何时甚至是否执行finalize()

例如:

如果您的程序在垃圾回收发生之前结束,则finalize()将不会执行。因此,它应该作为备份过程用于确保其他资源的正确处理,或者用于特殊的应用程序,而不是作为程序在正常操作中使用的手段。


0

https://wiki.sei.cmu.edu/confluence/display/java/MET12-J.+Do+not+use+finalizers所指出:

由于Java虚拟机(JVM)的执行时间不定,因此没有固定的时间来执行终结器。唯一的保证是任何执行终结器方法都将在关联对象变得不可达之后的某个时刻执行(在垃圾回收的第一个循环中检测到),并且在垃圾收集器的第二个循环期间回收关联对象的存储之前的某个时刻执行。对象的终结器执行可能会在对象变得不可达之后任意长的时间内被延迟。因此,在对象的finalize()方法中调用时关键功能,例如关闭文件句柄是有问题的。


0
Java允许对象实现一个名为finalize()的方法,该方法可能会被调用。如果垃圾收集器尝试回收对象,则会调用finalize()方法。如果垃圾收集器没有运行,则不会调用该方法。如果垃圾收集器无法收集对象并尝试再次运行它,则在第二次中不会调用该方法。在实践中,您极有可能不会在实际项目中使用它。只需记住它可能不会被调用,并且肯定不会被调用两次。finalize()方法可能会运行零次或一次。在下面的代码中,当我们运行它时,由于程序在需要运行垃圾收集器之前退出,因此finalize()方法不会产生任何输出。来源

-3

尝试运行此程序以更好地理解

public class FinalizeTest 
{       
    static {
        System.out.println(Runtime.getRuntime().freeMemory());
    }

    public void run() {
        System.out.println("run");
        System.out.println(Runtime.getRuntime().freeMemory());
    }

     protected void finalize() throws Throwable { 
         System.out.println("finalize");
         while(true)
             break;          
     }

     public static void main(String[] args) {
            for (int i = 0 ; i < 500000 ; i++ ) {
                    new FinalizeTest().run();
            }
     }
}

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