什么时候在 Java 中调用 finalize() 方法?

When is the finalize() method called in Java?

提问人:Rajesh Kumar J 提问时间:3/24/2010 最后编辑:Chris MartinRajesh Kumar J 更新时间:4/7/2022 访问量:530711

问:

我需要知道何时在 .我创建了一个测试类,当调用方法时,它通过重写它来写入文件。它不会被执行。谁能告诉我它不执行的原因?JVMfinalize()

Java 方法 垃圾回收 调用 finalize

评论

40赞 Reg 10/27/2017
顺便说一句:finalize 在 Java 9 中被标记为已弃用
2赞 wleao 12/3/2017
看一看: infoq.com/news/2017/03/Java-Finalize-Deprecated
0赞 ha9u63a7 1/11/2018
如果有任何内容引用了您的对象甚至类。 垃圾回收没有任何影响。finalize()
1赞 srk 6/25/2020
它从 Java9 开始被弃用。docs.oracle.com/javase/9/docs/api/java/lang/......
0赞 Thorbjørn Ravn Andersen 4/4/2022
截至 openjdk.java.net/jeps/421 年 (Java 18),终结器尚未删除。

答:

398赞 Joachim Sauer 3/24/2010 #1

当对象即将进行垃圾回收时,将调用该方法。这可以在它符合垃圾回收条件后的任何时间进行。finalize

请注意,一个对象完全有可能永远不会被垃圾回收(因此永远不会被调用)。当对象永远不会符合 gc 的条件时(因为它在 JVM 的整个生命周期内都可以访问),或者当对象符合条件和 JVM 停止运行之间没有实际运行垃圾回收时(这通常发生在简单的测试程序中),就会发生这种情况。finalize

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

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

评论

2赞 ChrisCantrell 9/26/2013
@Rajesh。不。这不是“寿命”问题。你可以把你的程序放在一个无限循环中(多年),如果不需要垃圾回收器,它永远不会运行。
5赞 Joachim Sauer 6/13/2014
@VikasVerma:完美的替代品什么都不是:你不应该需要它们。唯一有意义的情况是,如果你的类管理一些外部资源(如TCP/IP连接,文件......Java GC 无法处理的任何内容)。在这些情况下,接口(及其背后的想法)可能是您想要的:关闭/丢弃资源,并要求类的用户在正确的时间调用它。您可能希望添加一个“只是为了保存”的方法,但这更像是一个调试工具,而不是实际的修复(因为它不够可靠)。Closable.close()finalize
4赞 Joachim Sauer 2/13/2015
@Dragonborn:这实际上是一个完全不同的问题,应该单独提出。有关闭钩子,但如果 JVM 意外关闭(又名崩溃),则不能保证它们。但它们的保证比终结者的保证要强得多(而且它们也更安全)。
4赞 Jeroen Vannevel 4/18/2015
你的最后一段说只用它来清理资源,即使不能保证它会被调用。这是乐观吗?我认为这样不可靠的东西也不适合清理资源。
4赞 Elist 8/8/2018
终结者有时可以挽救局面......我遇到过一个案例,第三方库使用 FileInputStream,但从未关闭它。我的代码调用了库的代码,然后尝试移动文件,但失败了,因为它仍然处于打开状态。我必须强制调用调用FileInputStream::finalize(),然后才能移动文件。System.gc()
16赞 Stephen C 3/24/2010 #2

什么时候在 Java 中调用该方法?finalize()

finalize 方法将在 GC 检测到对象不再可访问之后,在实际回收对象使用的内存之前调用。

  • 如果一个对象永远不会变得无法访问,则永远不会被调用。finalize()

  • 如果 GC 未运行,则可能永远不会被调用。(通常,只有当 JVM 认为可能有足够的垃圾使其值得时,GC 才会运行。finalize()

  • 可能需要多个 GC 周期才能确定特定对象无法访问。(Java GC 通常是“分代”收集器......

  • 一旦 GC 检测到某个对象无法访问且可完成,它就会被放置在完成队列中。最终确定通常与普通 GC 异步进行。

(JVM 规范实际上允许 JVM 从不运行终结器......前提是它不会回收对象使用的空间。以这种方式实现的 JVM 将瘫痪/无用,但这种行为是“允许的”。

结果是,依靠最终确定来做必须在确定的时间范围内完成的事情是不明智的。完全不使用它们是“最佳实践”。应该有一种更好(即更可靠)的方法来做你想在方法中做的任何事情。finalize()

终结的唯一合法用途是清理与应用程序代码丢失的对象关联的资源。即便如此,您也应该尝试编写应用程序代码,以便它首先不会丢失对象。(例如,使用 Java 7+ try-with-resources 来确保始终调用 ...)close()


我创建了一个测试类,当调用 finalize() 方法时,它通过重写它来写入文件。它不会被执行。谁能告诉我它不执行的原因?

这很难说,但有几种可能性:

  • 该对象不会被垃圾回收,因为它仍然可以访问。
  • 该对象不会进行垃圾回收,因为 GC 在测试完成之前不会运行。
  • GC 会找到该对象,并由 GC 将其放入最终确定队列中,但在测试完成之前不会完成最终确定。
27赞 rsp 3/24/2010 #3

Java 方法不是析构函数,不应用于处理应用程序所依赖的逻辑。Java 规范指出,不能保证在应用程序的生存期间调用该方法。finalize()finalize

您可能想要的是 和 的组合 和清理方法,如:finally

MyClass myObj;

try {
    myObj = new MyClass();

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

这将正确处理构造函数引发异常时的情况。MyClass()

评论

0赞 rsp 11/27/2020
@Anatoly编辑错误,它将错过构造函数引发异常的情况。这将在新版本的示例中触发 NPE。MyClass()
294赞 user7094 3/24/2010 #4

一般来说,最好不要依赖做任何清理等。finalize()

根据 Javadoc(值得一读),它是:

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

正如 Joachim 所指出的,如果对象始终是可访问的,那么在程序的生命周期中,这可能永远不会发生。

此外,不保证垃圾回收器在任何特定时间运行。一般来说,我想说的是,除非你有特定的东西需要它,否则可能不是最好的方法。finalize()

评论

19赞 Mark Jeronimus 5/7/2012
换句话说(只是为了向未来的读者澄清),它永远不会在主类上调用,因为当主类关闭时,不需要收集垃圾。无论如何,操作系统都会清理应用程序使用的所有内容。
122赞 B T 3/8/2013
“不是最好的使用方法......除非有特定的东西需要它“——呃,这句话 100% 适用于所有事情,所以没有帮助。约阿希姆·绍尔(Joachim Sauer)的答案要好得多
1赞 Tom G 2/16/2014
@Zom-B 您的示例有助于澄清,但只是为了迂腐,如果主类创建一个非守护程序线程然后返回,大概可以在主类上调用它?
3赞 nbro 5/22/2015
那么,在哪些情况下会有用呢?finalize
3赞 Stephen C 9/10/2016
@MarkJeronimus - 实际上,这无关紧要。当垃圾回收类的 >>instance<< 时,而不是在 main 方法终止时,将调用主类的 on 方法。此外,主类可以在应用程序完成之前进行垃圾收集;例如,在多线程应用程序中,“主”线程创建其他线程,然后返回。(在实践中,需要一个非标准的类加载器......finalize()
78赞 XpiritO 3/24/2010 #5
protected void finalize() throws Throwable {}
  • 每个类都继承了该方法 java.lang.对象finalize()
  • 垃圾回收器在确定 不再引用该对象 存在
  • Object finalize 方法不执行任何操作,但可以由 任何班级
  • 通常,它应该被覆盖以清理非 Java 资源,即关闭 一个文件
  • 如果重写,最好使用 try-catch-finally 语句和 总是打电话给 .这 是确保您这样做的安全措施 不经意间错过关闭 调用的对象使用的资源 类finalize()super.finalize()

    protected void finalize() throws Throwable {
         try {
             close();        // close open files
         } finally {
             super.finalize();
         }
     }
    
  • 在垃圾回收期间引发的任何异常都会停止 最终确定,但否则被忽略finalize()

  • finalize()永远不会在任何对象上运行多次

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

您也可以查看这篇文章:

评论

28赞 Greg Chabala 6/15/2011
您链接到的 JavaWorld 文章是 1998 年的,其中有一些有趣的建议,特别是建议调用 System.runFinalizersOnExit() 以确保终结器在 JVM 退出之前运行。该方法目前已弃用,并带有注释“此方法本质上是不安全的。这可能会导致在活动对象上调用终结器,而其他线程同时操作这些对象,从而导致行为不稳定或死锁。所以我不会那样做。
3赞 Ustaman Sangat 7/25/2012
由于 runFinalizerOnExit() 不是线程安全的,因此可以做的是 Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { destroyMyEnclosingClass();在类的构造函数中。
3赞 pieroxy 6/19/2013
@Ustaman Sangat 这是一种方法,但请记住,这会从 shutdownHook 设置对实例的引用,这几乎可以保证您的类永远不会被垃圾回收。换句话说,这是内存泄漏。
1赞 Ustaman Sangat 6/20/2013
@pieroxy,虽然我同意这里的其他人关于不对任何事情使用 finalize() 的观点,但我不明白为什么必须从关闭钩子中引用。可以有一个软参考。
2赞 giri 3/24/2010 #6

不保证 finalize 方法。当对象符合 GC 条件时,将调用此方法。在许多情况下,对象可能不会被垃圾回收。

评论

4赞 Stephen C 1/22/2017
不對。你说的是,当一个对象变得无法访问时,它就完成了。它实际上是在实际收集方法时调用的。
5赞 user1623624 11/25/2012 #7

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
2赞 Tushar Trivedi 4/9/2013 #8

如果无法从任何实时线程或任何静态引用访问对象,则该对象符合垃圾回收或 GC 的条件,换句话说,如果对象的所有引用都为 null,则可以说该对象符合垃圾回收的条件。循环依赖项不计为引用,因此,如果对象 A 具有对象 B 的引用,对象 B 具有对象 A 的引用,并且它们没有任何其他实时引用,则对象 A 和 B 都有资格进行垃圾回收。 通常,在以下情况下,对象有资格在 Java 中进行垃圾回收:

  1. 该对象的所有引用都显式设置为 null,例如 object = null
  2. 对象在块内创建,一旦控制退出该块,引用就会超出范围。
  3. 如果一个对象包含另一个对象的引用,并且当您设置容器对象的引用 null 时,父对象设置为 null,则子对象或包含对象将自动符合垃圾回收的条件。
  4. 如果一个对象只有通过 WeakHashMap 的实时引用,它将有资格进行垃圾回收。

评论

0赞 supercat 4/28/2017
如果一个对象的字段在慢速计算过程中被重复使用,并且该对象在此之后永远不会被使用,那么该对象是否会一直保持活动状态,直到源代码请求该字段的最后一个,或者 JIT 是否可以将该字段复制到临时变量中,然后在计算前放弃该对象?final
0赞 Holger 5/4/2017
@supercat:优化器可能会将代码重写为最初未创建对象的表单;在这种情况下,它可能会在其构造函数完成后立即完成,除非同步强制对象的使用和终结器之间的排序。
0赞 supercat 5/5/2017
@Holger:如果 JIT 可以看到一个对象在创建和放弃之间将要发生的一切,我认为 JIT 没有任何理由提前触发终结器。真正的问题是代码需要做什么来确保终结器不能在特定方法中触发。在.NET中,有一个函数除了强制GC假设它可能使用对象之外什么都不做,但我知道Java中没有这样的函数。人们可以为此目的使用变量,但仅将变量用于此目的似乎是浪费。GC.KeepAlive()volatile
0赞 Holger 5/5/2017
@supercat:JIT 不会触发终结,它只是将代码安排为不保留引用,但是,直接将 排入队列可能是有益的,这样它就不需要 GC 循环来发现没有引用。同步足以确保先发生关系;由于最终确定可能(实际上)在不同的线程中运行,因此无论如何,它通常在形式上是必要的。Java 9 将添加 Reference.reachabilityFence...FinalizerReference
0赞 supercat 5/5/2017
@Holger:如果 JIT 优化了对象创建,那么唯一会被调用的是 JIT 生成直接这样做的代码。对于只期望在单个线程中使用的对象,通常是否需要同步代码?如果一个对象在被放弃之前执行了一些需要撤消的操作(例如,打开套接字连接并获得对另一端资源的独占使用权),那么在代码仍在使用套接字时让终结器关闭连接将是一场灾难。代码使用同步是正常的吗?Finalize
20赞 Hao Deng 7/1/2013 #9

查看 Effective Java,第 2 版,第 27 页。第 7 项:避免终结者

终结器是不可预测的,通常是危险的,而且通常是不必要的。永远不要在终结器中做任何时间紧迫的事情。从不 依靠终结器来更新关键持久状态。

若要终止资源,请改用 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赞 Gabriel Garcia 5/12/2017
或使用 try-with-resources
3赞 Erik Aronesty 10/26/2018
这假定对象的生存期在一个函数的范围内。当然,这不是 OP 所指的情况,也不是基本上任何人都需要的情况。想想“缓存引擎返回值引用计数”。您希望在最后一个 ref 被释放时释放缓存条目,但您不知道最后一个 ref 何时被释放。finalize() 可以减少 ref 计数,例如...但是,如果您要求用户显式调用免费函数,则要求内存泄漏。通常我只是两者兼而有之(发布功能+仔细检查最终确定......
10赞 techloris_109 5/13/2015 #10

由于 JVM 调用 finalize() 方法存在不确定性(不确定是否会执行被覆盖的 finalize(),因此出于研究目的,观察调用 finalize() 时会发生什么情况的更好方法是强制 JVM 通过命令调用垃圾回收。System.gc()

具体来说,当对象不再使用时,会调用 finalize()。但是,当我们试图通过创建新对象来调用它时,它的调用是不确定的。因此,为了确定起见,我们创建了一个显然没有未来用途的对象,因此我们看到该对象的 finalize 调用。nullcc

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,也不确定 b 是否被 JVM 清除,因为“在 Bike 类中调用了 finalize 方法...”未打印。

评论

10赞 Simon Forsberg 9/21/2015
调用并不能保证垃圾回收将实际运行。System.gc();
3赞 Stephen C 1/22/2017
也不能保证将运行哪种类型的收集。相关,因为大多数 Java GC 都是“分代”收集器。
4赞 Martin Kersten 9/25/2015 #11

最近一直在与终结器方法搏斗(为了在测试期间处理连接池),我不得不说终结器缺少很多东西。使用 VisualVM 进行观察以及使用弱引用来跟踪实际交互,我发现在 Java 8 环境(Oracle JDK、Ubuntu 15)中,以下情况是正确的:

  • Finalize 不会立即调用,Finalizer(GC 部分)单独拥有难以捉摸的引用
  • 默认的垃圾回收器池无法访问的对象
  • 批量调用 Finalize,指向一个实现细节,即垃圾回收器在某个阶段释放资源。
  • 调用 System.gc() 通常不会导致对象更频繁地完成,它只会导致 Finalizer 更快地意识到无法访问的对象
  • 创建线程转储几乎总是会导致触发终结器,因为在执行堆转储或其他一些内部机制期间堆开销很高
  • 定版接缝受制于内存要求(释放更多内存)或被标记为定版的对象列表,这些对象的增长超过一定的内部限制。因此,如果您有很多对象被定稿,那么与只有少数对象相比,定稿阶段将更频繁、更早地触发
  • 在某些情况下,System.gc() 会直接触发 finalize,但前提是引用是本地的和短暂的。这可能与世代有关。

最后的想法

Finalize 方法不可靠,但只能用于一件事。您可以确保在垃圾回收之前关闭或处置对象,从而在正确处理具有更复杂的生命周期(涉及生命周期结束操作)的对象时实现故障保护。这是我能想到的一个原因,它值得为了覆盖它。

1赞 pradeep 2/1/2016 #12

我们重写 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");
    }
}

最终确定方法被调用的几率

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

当内存中充斥着转储对象时,GC 将调用 finalize 方法

运行并查看控制台,在控制台中找不到频繁调用的 finalize 方法,当内存过载时,将调用 finalize 方法。

0赞 JavaDev 7/25/2016 #13

Java 允许对象实现一个名为 finalize() 的方法 这可能会被调用。

如果垃圾回收器尝试 收集对象。

如果垃圾回收器未运行,则不会调用该方法。

如果垃圾回收器无法收集对象并尝试再次运行它,则不会第二次调用该方法。

在实践中,您极不可能在实际项目中使用它。

请记住,它可能不会被调用,而且肯定会被调用 不会被叫两次。finalize() 方法可以运行 0 或 1 时间。

在下面的代码中,finalize() 方法在以下情况下不产生输出 运行它,因为程序在需要运行 垃圾回收器。

-3赞 user3836455 1/17/2017 #14

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

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();
            }
     }
}
2赞 Amarildo 7/3/2017 #15

有时,当它被破坏时,一个对象必须采取行动。例如,如果对象具有非 Java 资源(如文件句柄或字体),则可以在销毁对象之前验证这些资源是否已释放。为了管理这种情况,java 提供了一种称为“终结”的机制。通过完成它,您可以定义当对象即将从垃圾回收器中删除时发生的特定操作。 要向类添加终结器,只需定义 finalize() 方法即可。每当 Java 执行时要删除该类的对象时,它都会调用此方法。在 finalize method() 中,您可以指定在销毁对象之前要执行的操作。 定期搜索垃圾回收器,以查找不再引用任何运行状态或间接引用任何其他对象的对象。在释放资产之前,Java 运行时会对对象调用 finalize() 方法。finalize() 方法的一般形式如下:

protected void finalize(){
    // This is where the finalization code is entered
}

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

1赞 AyukNayr 4/17/2018 #16

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

例:

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

0赞 Utkarsh 11/30/2019 #17

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

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

2赞 Panagiotis Bougioukos 4/7/2022 #18

JDK 18 的最新消息

根据 openjdk 18 上交付的 JEPS 421,最终确定方法的功能将被标记为意味着永久删除将在 jdk 18 之后的某个更高版本中出现。finalize()deprecated(forRemoval=true)

从 jdk 18 开始,一个新的命令行选项在任何地方禁用终结机制,甚至对于 jdk 本身内部的声明也是如此。--finalization=disabled

这也与这里的问题有关,因为它计划删除的原因是它包含的一些主要缺陷。其中一个缺陷是,从对象变得无法访问的那一刻到调用其终结器的那一刻之间可能会经过很长时间。同样,GC 不保证任何终结器都会被调用。