【问题标题】:When is the finalize() method called in Java?Java 什么时候调用 finalize() 方法?
【发布时间】:2018-01-01 02:25:34
【问题描述】:

我需要知道finalize() 方法何时在JVM 中被调用。我创建了一个测试类,当通过覆盖它调用finalize() 方法时写入文件。它不被执行。谁能告诉我它没有执行的原因吗?

【问题讨论】:

标签: java methods garbage-collection call finalize


【解决方案1】:

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

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

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

如果您依赖finalize 来正确运行您的应用程序,那么您做错了什么。 finalize 应该用于清理(通常是非 Java)资源。 完全正确,因为 JVM 不保证 finalize 会被任何对象调用。

【讨论】:

  • @Rajesh。不,这不是“寿命”问题。您可以将程序置于无限循环中(多年),如果不需要垃圾收集器,它将永远不会运行。
  • @VikasVerma:完美的替代品没什么:你不应该需要它们。唯一有意义的情况是,如果您的类管理一些外部资源(如 TCP/IP 连接、文件……Java GC 无法处理的任何东西)。在这些情况下,Closable 接口(及其背后的想法)可能就是您想要的:使.close() 关闭/丢弃资源并要求您的类的用户在正确的时间调用它。您可能想要添加一个finalize 方法“只是为了保存”,但这更像是一个调试工具而不是实际修复(因为它不够可靠)。
  • @Dragonborn:这实际上是一个完全不同的问题,应该单独提出。有关闭挂钩,但如果 JVM 意外关闭(又名崩溃),则无法保证它们。但是它们的保证比终结器的保证要强得多(而且它们也更安全)。
  • 你的最后一段说它只用于清理资源,即使不能保证它会被调用。这种乐观是在说话吗?我会假设一些不可靠的东西也不适合清理资源。
  • 终结者有时可以挽救这一天...我有一个案例,第 3 方库正在使用 FileInputStream,从不关闭它。我的代码正在调用库的代码,然后尝试移动文件,但由于它仍处于打开状态而失败。我不得不强制调用 System.gc() 来调用 FileInputStream::finalize(),然后我才能移动文件。
【解决方案2】:

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

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

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

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

此外,垃圾收集器不能保证在任何特定时间运行。一般来说,我想说的是finalize() 可能不是一般使用的最佳方法,除非有特定的需要。

【讨论】:

  • 换句话说(只是为了向未来的读者澄清)它永远不会在主类上调用,因为当主类关闭时,不需要收集垃圾。操作系统会清理应用程序使用的所有内容。
  • “不是最好的使用方法……除非你有特定的需要”——嗯,这句话适用于所有事情的 100%,所以没有帮助。 Joachim Sauer 的回答要好得多
  • @Zom-B 您的示例有助于澄清,但只是为了迂腐,如果主类创建一个非守护线程然后返回,大概可以在主类上调用它?跨度>
  • 那么finalize在什么情况下有用?
  • @MarkJeronimus - 实际上,这无关紧要。主类的 finalize() 方法在类的 >>instance可能在应用程序完成之前被垃圾收集;例如在“主”线程创建其他线程然后返回的多线程应用程序中。 (实际上,需要一个非标准的类加载器......)
【解决方案3】:
protected void finalize() throws Throwable {}
  • 每个类都继承了finalize() 方法 java.lang.Object
  • 垃圾收集器在确定时调用该方法 不再引用该对象 存在
  • Object finalize 方法不执行任何操作,但它可能会被 任何课程
  • 通常应该覆盖它以清理非 Java 资源,即关闭 一个文件
  • 如果覆盖finalize(),使用 try-catch-finally 语句和 总是打电话给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

您也可以查看这篇文章:

【讨论】:

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

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

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

MyClass myObj;

try {
    myObj = new MyClass();

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

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

【讨论】:

  • @Anatoly 编辑错误,错过了MyClass() 构造函数抛出异常的情况。这将在新版本的示例中触发 NPE。
【解决方案5】:

查看 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
}

【讨论】:

  • 或使用 try-with-resources
  • 这假定一个对象的生命周期在一个函数的范围内。当然,这不是 OP 所指的情况,也不是任何人都需要的情况。想想“缓存引擎返回值引用计数”。您想在最后一个 ref 被释放时释放缓存条目,但您不知道最后一个 ref 何时被释放。 finalize() 可以减少引用计数,例如......但是如果您要求用户显式调用免费函数,则您要求内存泄漏。通常我两者都做(发布功能+双重检查最终确定......)......
【解决方案6】:

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

在 GC 检测到对象不再可达之后,并且在它真正回收对象使用的内存之前,将调用 finalize 方法。

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

  • 如果 GC 未运行,则可能永远不会调用 finalize()。 (通常,GC 仅在 JVM 确定可能有足够多的垃圾使其值得时才运行。)

  • 在 GC 确定特定对象不可达之前,可能需要多个 GC 周期。 (Java GC 通常是“世代”收集器...)

  • 一旦 GC 检测到一个对象不可访问且可终结,它就会被放入终结队列。终结通常与正常的 GC 异步发生。

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

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

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


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

很难说,但有几种可能:

  • 对象未被垃圾回收,因为它仍然可以访问。
  • 对象未被垃圾回收,因为在您的测试完成之前 GC 没有运行。
  • 对象被 GC 发现并被 GC 放入终结队列中,但在您的测试完成之前终结并未完成。

【讨论】:

    【解决方案7】:

    由于 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 之后,由于“在 Bike 类中调用了 finalize 方法,JVM 是否清除了 b 也存在不确定性。 .." 没有打印出来。

    【讨论】:

    • 调用 System.gc(); 并不能保证垃圾回收会实际运行。
    • 也不保证会运行什么种类的集合。相关,因为大多数 Java GC 都是“世代”收集器。
    【解决方案8】:

    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
    

    【讨论】:

      【解决方案9】:

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

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

      最后的想法

      Finalize 方法不可靠,但只能用于一件事。您可以确保对象在被垃圾收集之前已关闭或处置,如果正确处理涉及生命周期结束操作的具有更复杂生命周期的对象,则可以实现故障安全。这是我能想到的让它值得覆盖它的一个原因。

      【讨论】:

        【解决方案10】:

        如果一个对象不能从任何活动线程或任何静态引用访问,则该对象有资格进行垃圾收集或 GC,换句话说,如果一个对象的所有引用都为空,则可以说该对象有资格进行垃圾收集。循环依赖不计为引用,因此如果对象 A 具有对象 B 的引用并且对象 B 具有对象 A 的引用并且它们没有任何其他实时引用,那么对象 A 和 B 都将有资格进行垃圾收集。 通常,在以下情况下,对象可以在 Java 中进行垃圾回收:

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

        【讨论】:

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

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

        【讨论】:

        • 不正确。你的意思是当一个对象变得无法访问时,它就被最终确定了。在实际收集方法时实际调用它。
        【解决方案12】:

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

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

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

        【讨论】:

          【解决方案13】:

          我们覆盖 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();
                  }
              }
          }
          

          当内存被转储对象超载时,gc 将调用 finalize 方法

          运行并查看控制台,您不会发现 finalize 方法被频繁调用,当内存过载时,将调用 finalize 方法。

          【讨论】:

            【解决方案14】:

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

            finalize() 方法在垃圾收集器尝试时被调用 收集物品。

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

            如果垃圾收集器未能收集到对象并尝试运行 再一次,第二次没有调用该方法。

            实际上,您不太可能在实际项目中使用它。

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

            在下面的代码中,finalize() 方法在我们执行时没有输出 运行它,因为程序在需要运行之前退出 垃圾收集器。

            Source

            【讨论】:

              【解决方案15】:

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

              例子:

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

              【讨论】:

                【解决方案16】:

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

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

                【讨论】:

                  【解决方案17】:

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

                  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();
                              }
                       }
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2012-05-16
                    • 2011-09-19
                    • 2011-07-21
                    • 1970-01-01
                    • 2015-07-13
                    • 2012-11-30
                    相关资源
                    最近更新 更多