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个回答

398
finalize方法在对象准备进行垃圾回收时被调用。这可能是在对象符合垃圾回收条件后的任何时间。

请注意,一个对象可能永远不会被垃圾收集(因此也不会调用finalize)。这种情况可能发生在对象从未符合垃圾回收条件(因为它在JVM的整个生命周期内都是可达的)或当对象变得符合条件与JVM停止运行之间没有进行垃圾回收(这通常出现在简单的测试程序中)。

有办法告诉JVM在尚未调用finalize的对象上运行它,但使用它们也不是一个好主意(那个方法的保证也不是非常强)。

如果你依赖finalize来确保应用程序的正确操作,那么你做错了什么。 finalize应该用于清理(通常是非Java的)资源。这正是因为JVM不能保证在任何对象上调用finalize


2
@Rajesh。不,这不是一个“寿命”问题。你可以将你的程序放入一个无限循环(多年),如果垃圾收集器不需要它就永远不会运行。 - ChrisCantrell
5
完美的替代品就是没有:你不应该需要它们。唯一有道理的情况是,如果你的类管理一些外部资源(如TCP/IP连接、文件……任何Java GC无法处理的东西)。在这种情况下,Closeable接口(及其背后的思想)可能是你想要的:使.close()关闭/丢弃资源,并要求你的类的用户在正确时间调用它。你可能希望添加一个"finalize"方法“只是为了安全起见”,但那更多的是一个调试工具而不是一个实际的修复(因为它不够可靠)。 - Joachim Sauer
4
@Dragonborn:那实际上是一个非常不同的问题,应该单独提出来询问。有关闭钩子(shutdown hooks),但如果JVM意外关闭(也就是崩溃),它们不能保证一定会执行。但它们的保证要比终结器(finalizers)强得多(而且更安全)。 - Joachim Sauer
4
你最后一段说只应该将其用于清理资源,即使没有保证它会被调用。这是乐观主义在说话吗?我认为像这样不可靠的东西也不适合用于清理资源。 - Jeroen Vannevel
4
终结器有时可以救急... 我遇到过这样的情况:一个第三方库使用了FileInputStream,但从未关闭它。我的代码调用了该库的代码,然后尝试移动文件,但失败了,因为文件仍处于打开状态。我不得不强制调用System.gc()来调用FileInputStream::finalize(),然后才能移动文件。 - Elist
显示剩余7条评论

294
通常最好不要依赖于finalize()来进行任何清理等操作。
根据Javadoc(值得一读),它是:

当垃圾回收确定没有对该对象的引用时,由垃圾回收器在对象上调用。

正如Joachim所指出的那样,如果对象始终可访问,则在程序生命周期内可能永远不会发生这种情况。
此外,不能保证垃圾回收器在任何特定时间运行。总的来说,我的意思是,除非有特定的需求,否则finalize()可能不是通用的最佳方法。

19
换句话说(为了方便未来的读者理解),主类不会被调用,因为当主类关闭时,不需要收集垃圾。操作系统会清理应用程序使用的所有内容。 - Mark Jeronimus
122
“不是最好的方法...除非你需要它来完成某些特定的任务” - 嗯,这个句子适用于百分之百的情况,所以并不太有帮助。Joachim Sauer的回答更好。 - B T
1
@Zom-B,你的例子对于澄清很有帮助,但是为了严谨起见,如果主类创建了一个非守护线程并返回,那么它可以被称为主类调用吗? - Tom G
3
finalize方法在哪些情况下会有用? - nbro
3
@MarkJeronimus - 实际上,这并不相关。当类的一个实例被垃圾收集时,主类中的finalize()方法会被调用,而不是当主方法终止时调用。此外,主类在应用程序完成之前可能会被垃圾收集;比如,在多线程应用中,“主”线程创建其他线程然后返回。(实际上,需要使用非标准类加载器...) - Stephen C
显示剩余5条评论

78
protected void finalize() throws Throwable {}
  • every class inherits the finalize() method from java.lang.Object
  • the method is called by the garbage collector when it determines no more references to the object exist
  • the Object finalize method performs no actions but it may be overridden by any class
  • normally it should be overridden to clean-up non-Java resources ie closing a file
  • if overridding finalize() it is good programming practice to use a try-catch-finally statement and to always call super.finalize(). This is a safety measure to ensure you do not inadvertently miss closing a resource used by the objects calling class

    protected void finalize() throws Throwable {
         try {
             close();        // close open files
         } finally {
             super.finalize();
         }
     }
    
  • any exception thrown by finalize() during garbage collection halts the finalization but is otherwise ignored

  • finalize() is never run more than once on any object

引用自: http://www.janeg.ca/scjp/gc/finalize.html

你也可以查看这篇文章:


28
您提供的JavaWorld文章链接是1998年的,其中有一些有趣的建议,特别是建议调用System.runFinalizersOnExit()方法以确保在JVM退出前运行finalizer。该方法目前已被弃用,并带有注释:“此方法本质上是不安全的。它可能导致在其他线程同时操作这些对象时对活动对象进行finalizer调用,从而导致不稳定的行为或死锁。”因此,我将不会这样做。 - Greg Chabala
3
由于 runFinalizerOnExit() 不具备线程安全性,可以在类的构造函数中使用 Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { destroyMyEnclosingClass(); } }); 来代替。这样做可以保证在 JVM 关闭时销毁该类的实例。 - Ustaman Sangat
3
@Ustaman Sangat,这是一种做法,但请记住,在shutdownHook中设置对你的实例的引用几乎保证了你的类永远不会被垃圾回收。换句话说,这是内存泄漏。 - pieroxy
1
@pieroxy,虽然我同意这里其他人关于不使用finalize()的观点,但我不明白为什么关闭钩子需要引用。可以使用软引用。 - Ustaman Sangat

27

Java中的finalize()方法不是解构函数,不能用于处理应用程序依赖的逻辑。Java规范说明,在应用程序的生命周期内不能保证finalize方法被调用。

你可能需要的是finally和清理方法的组合,如下:

MyClass myObj;

try {
    myObj = new MyClass();

    // ...
} finally {
    if (null != myObj) {
        myObj.cleanup();
    }
}

MyClass()构造函数抛出异常时,这将正确处理该情况。


@Anatoly,这个编辑是错误的,它忽略了MyClass()构造函数抛出异常的情况。这将在示例的新版本中触发NPE。 - rsp

20

请参阅Effective Java第二版的第27页。 第7条:避免使用finalizer(终结方法)

finalizer是不可预测的、常常是危险的,而且通常也是不必要的。在finalizer中不要执行任何时间关键的操作,在finalizer中也不要依赖于更新关键持久状态。

要终止资源,请改用try-finally:

// try-finally block guarantees execution of termination method
Foo foo = new Foo(...);
try {
    // Do what must be done with foo
    ...
} finally {
    foo.terminate(); // Explicit termination method
}

3
可以使用try-with-resources。 - Gabriel Garcia
3
这假定一个对象的生命周期在一个函数的作用域内。当然,这不是OP所指的情况,也不是基本上任何人需要的情况。想想“缓存引擎返回值引用计数”。当最后一次引用被释放时,您想释放缓存条目,但是您不知道最后一次引用何时被释放。例如finalize()可以减少引用计数...但如果您要求用户显式调用free()函数,则可能导致内存泄漏。通常我只做两者(释放函数+在finalize()中进行双重检查...)。 - Erik Aronesty

16
在Java中,finalize() 方法是在对象被垃圾收集器检测到不再可达时调用,并在回收对象使用的内存之前调用。
  • 如果一个对象永远不会变得不可达,则永远不会对其调用finalize()方法。

  • 如果垃圾收集器没有运行,则可能永远不会调用finalize()方法。(通常,只有当JVM判断可能存在足够多的垃圾时,才会运行垃圾收集器。)

  • 可能需要多个垃圾收集周期才能确定特定对象已不可达。(Java垃圾回收器通常是“分代”收集器...)

  • 一旦垃圾收集器检测到对象不可达且可终结,它就会将其放置在终结队列上。终结通常与正常的垃圾回收异步发生。

(JVM规范实际上允许JVM永远不运行finalizer,只要它不回收对象使用的空间。以这种方式实现的JVM将被禁用/无用,但此行为是“允许”的。)

总之,依赖终结来完成必须在明确时间范围内完成的事情是不明智的。最好根本不使用他们。应该有更好(即更可靠)的方法来完成您要在finalize()方法中尝试完成的任何操作。
终结的唯一合法用途是清除由应用代码丢失的对象关联的资源。即使这样,您也应该尝试编写应用代码,以便首先不会丢失对象。(例如,使用Java 7+ try-with-resources 来确保始终调用close() ...)我创建了一个测试类,当覆盖finalize()方法时会将内容写入文件。但是它没有被执行。有可能的原因有以下几种:
  • 对象仍然可以被引用,所以没有被垃圾回收。
  • 在测试结束之前垃圾回收程序没有运行。
  • 垃圾回收程序找到该对象并将其放置在最终化队列中,但在测试结束之前最终化没有完成。

10

由于JVM对finalize()方法的调用存在不确定性(无法确定是否执行重写的finalize()方法),因此为了研究目的,更好的观察finalize()方法被调用时发生的情况是通过命令System.gc()强制JVM进行垃圾回收。

具体来说,当对象不再使用时,finalize()方法将被调用。但是,当我们尝试通过创建新对象来调用它时,无法确定其是否被调用。因此,为了确定性,我们创建一个无用的null对象c,因此我们可以看到对象c的finalize()方法的调用。

示例

class Car {

    int maxspeed;

    Car() {
        maxspeed = 70;
    }

    protected void finalize() {

    // Originally finalize method does nothing, but here we override finalize() saying it to print some stmt
    // Calling of finalize is uncertain. Difficult to observe so we force JVM to call it by System.gc(); GarbageCollection

        System.out.println("Called finalize method in class Car...");
    }
}

class Bike {

    int maxspeed;

    Bike() {
        maxspeed = 50;
    }

    protected void finalize() {
        System.out.println("Called finalize method in class Bike...");
    }
}

class Example {

    public static void main(String args[]) {
        Car c = new Car();
        c = null;    // if c weren`t null JVM wouldn't be certain it's cleared or not, null means has no future use or no longer in use hence clears it
        Bike b = new Bike();
        System.gc();    // should clear c, but not b
        for (b.maxspeed = 1; b.maxspeed <= 70; b.maxspeed++) {
            System.out.print("\t" + b.maxspeed);
            if (b.maxspeed > 50) {
                System.out.println("Over Speed. Pls slow down.");
            }
        }
    }
}

输出

    Called finalize method in class Car...
            1       2       3       4       5       6       7       8       9
    10      11      12      13      14      15      16      17      18      19
    20      21      22      23      24      25      26      27      28      29
    30      31      32      33      34      35      36      37      38      39
    40      41      42      43      44      45      46      47      48      49
    50      51Over Speed. Pls slow down.
            52Over Speed. Pls slow down.
            53Over Speed. Pls slow down.
            54Over Speed. Pls slow down.
            55Over Speed. Pls slow down.
            56Over Speed. Pls slow down.
            57Over Speed. Pls slow down.
            58Over Speed. Pls slow down. 
            59Over Speed. Pls slow down.
            60Over Speed. Pls slow down.
            61Over Speed. Pls slow down.
            62Over Speed. Pls slow down.
            63Over Speed. Pls slow down.
            64Over Speed. Pls slow down.
            65Over Speed. Pls slow down.
            66Over Speed. Pls slow down.
            67Over Speed. Pls slow down.
            68Over Speed. Pls slow down.
            69Over Speed. Pls slow down.
            70Over Speed. Pls slow down.

注意 - 即使打印了70次并且在程序中不再使用对象b,仍然存在JVM未清除b的不确定性,因为“调用Bike类中的finalize方法…”没有被打印。


10
调用 System.gc(); 并不能保证垃圾回收一定会被执行。 - Simon Forsberg
3
无法保证运行的是何种类型的集合,这一点也很重要,因为大多数Java GC(垃圾收集器)都是“分代”收集器。 - Stephen C

5

finalize将打印出类创建的计数。

protected void finalize() throws Throwable {
    System.out.println("Run F" );
    if ( checkedOut)
        System.out.println("Error: Checked out");
        System.out.println("Class Create Count: " + classCreate);
}

主函数

while ( true) {
    Book novel=new Book(true);
    //System.out.println(novel.checkedOut);
    //Runtime.getRuntime().runFinalization();
    novel.checkIn();
    new Book(true);
    //System.runFinalization();
    System.gc();

正如您所看到的。以下输出显示当类计数为36时,gc首次执行。

C:\javaCode\firstClass>java TerminationCondition
Run F
Error: Checked out
Class Create Count: 36
Run F
Error: Checked out
Class Create Count: 48
Run F

4
最近我在解决finalizer方法的问题(为了在测试期间释放连接池),我必须说,finalizer缺乏许多功能。使用VisualVM观察并使用弱引用跟踪实际交互,我发现在Java 8环境(Oracle JDK,Ubuntu 15)中以下事情是真实的:
  • 当Finalizer(GC部分)独占引用时,finalize不会立即被调用
  • 默认垃圾收集器池回收不可达对象
  • finalize以批量方式调用,指向某个实现细节,即垃圾收集器在某个阶段释放资源。
  • 经常调用System.gc()并不会导致对象更频繁地被finalize,它只会使Finalizer更快地意识到无法访问的对象
  • 几乎总是创建线程转储会因执行堆转储或其他内部机制而导致触发finalizer
  • 最终化似乎受内存要求(释放更多内存)或标记为最终化对象列表增长到某个内部限制的影响。因此,如果有许多对象正在完成最终化,与仅有少量对象相比,最终化阶段将更频繁地触发且提早触发
  • 有时System.gc()会直接触发finalize,但前提是引用是本地和短暂的。这可能与代相关。

最终思考

Finalize方法不可靠,但仅可用于一件事。您可以确保在对象被垃圾收集之前已关闭或释放,从而使得能够在正确处理涉及生命周期结束操作的对象的复杂生命周期的情况下实现故障安全。这是我所想到的唯一使它值得覆盖的原因。


2
有时,当一个对象被销毁时,它必须执行某些操作。例如,如果一个对象有一个非 Java 资源,比如文件句柄或字体,您可以验证这些资源在销毁对象之前是否被释放。为了管理这种情况,Java 提供了一种称为“finalizing”的机制。通过 finalizing,您可以定义特定的操作,在从垃圾收集器中删除对象时发生。 要向一个类添加 finalizer,只需定义 finalize() 方法。Java 执行时会在即将删除该类的对象时调用此方法。在 finalize 方法() 中,您可以指定在销毁对象之前要执行的操作。 垃圾收集器定期搜索不再引用任何运行状态或间接引用任何其他对象的对象。在释放资产之前,Java 运行时在对象上调用 finalize() 方法。 finalize() 方法的一般形式如下:
protected void finalize(){
    // This is where the finalization code is entered
}

使用protected关键字可以防止类外部的代码访问finalize()方法。需要注意的是,finalize()方法只会在垃圾回收之前被调用。例如,当一个对象离开作用域时,它不会被调用。这意味着您无法知道finalize()方法何时执行,甚至是否会执行。因此,程序必须提供其他方式来释放系统资源或对象使用的其他资源。您不应该依赖finalize()方法来正常运行程序。

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