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


330

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


34
正如一个侧面说明:在Java的9弃用的finalize标
注册


如果有任何东西引用了您的对象甚至是类。finalize()垃圾回收没有任何影响。
ha9u63ar

Answers:


273

通常,最好不要依赖finalize()进行任何清理等工作。

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

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

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

同样,不保证垃圾收集器在任何特定时间运行。总的来说,我想说的finalize()可能不是一般上最好的方法,除非您需要特定的东西。


19
换句话说(只是为了向以后的读者阐明),它永远不会在主类上调用,因为当主类关闭时,不需要收集垃圾。操作系统会清理应用程序始终使用的所有内容。
Mark Jeronimus 2012年

113
“不是最好的使用方法……除非您需要某些特定的东西”-嗯,该句子适用于所有内容的100%,因此无济于事。约阿希姆·绍尔(Joachim Sauer)的回答要好得多
英国电信

1
@ Zom-B您的示例有助于弄清楚,但只是为了简单而已,如果主类创建了非守护程序线程然后返回了,大概可以在主类上调用它了?
汤姆G

3
那么在什么情况下finalize有用呢?
nbro

3
@MarkJeronimus-实际上,这无关紧要。在finalize()对主类方法中,当类的一个实例>> <<被垃圾回收,主要方法终止时不被调用。此外,可以在应用程序完成之前对主类进行垃圾回收。例如,在多线程应用程序中,“主”线程创建其他线程,然后返回。(在实践中,将需要非标准的类加载器...。)
Stephen C

379

finalize当对象将要收集垃圾时调用该方法。在可以进行垃圾收集之后,可以随时进行。

请注意,对象很可能永远不会被垃圾回收(因此finalize永远不会被调用)。当对象从不符合gc资格(因为在JVM的整个生命周期内都是可以访问的)时,或者在对象符合资格的时间与JVM停止运行的时间之间没有运行垃圾回收时,通常会发生这种情况。测试程序)。

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

如果您依靠finalize应用程序的正确操作,那么您做错了什么。finalize应该用于清理(通常是非Java)资源。而这正是因为JVM没有这样的保证finalize是曾经把任何物体上。


2
@Rajesh。不。这不是一个“寿命”问题。您可以将程序置于无限循环中(多年),如果不需要垃圾收集器,它将永远不会运行。
ChrisCantrell 2013年

5
@VikasVerma:完美的替代品是什么:您不需要它们。唯一有意义的情况是您的类管理某些外部资源(例如TCP / IP连接,文件... Java GC无法处理的任何内容)。在这种情况下,Closable您可能需要接口(及其背后的想法):.close()关闭/丢弃资源,并要求类的用户在正确的时间调用它。您可能想要添加一种finalize“仅保存”的方法,但这将比实际的修复更多地是调试工具(因为它不够可靠)。
Joachim Sauer 2014年

4
@Dragonborn:这实际上是一个完全不同的问题,应该单独提出。有关闭挂钩,但是如果JVM意外关闭(也称为崩溃),则不能保证它们。但是它们的保证比终结器的保证要强得多(并且它们也更安全)。
Joachim Sauer

4
您的最后一段说,即使不能保证会调用它,也只能将其用于清理资源。这是乐观吗?我认为这是不可靠的,因为这也不适合清理资源。
Jeroen Vannevel 2015年

2
终结器有时可以节省一天的时间...我遇到了一个第三方库正在使用FileInputStream却从未关闭的情况。我的代码正在调用库的代码,然后尝试移动文件,但由于文件仍处于打开状态而失败。我必须强制调用System.gc()以调用FileInputStream :: finalize(),然后才能移动文件。
Elist

73
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

您还可以查看这篇文章:


25
您链接到的JavaWorld文章来自1998年,并提出了一些有趣的建议,特别是建议您调用System.runFinalizersOnExit()以确保终结器在JVM退出之前运行。该方法当前已被弃用,并带有注释“该方法本质上是不安全的。它可能导致在活动对象上调用终结器,而其他线程正在同时操纵这些对象,从而导致行为不稳定或死锁。所以我不会那样做。
格雷格·查巴拉2011年

3
由于runFinalizerOnExit()不是线程安全的,因此可以执行Runtime.getRuntime()。addShutdownHook(new Thread(){public void run(){destroyMyEnclosingClass();})); 在类的构造函数中。
乌斯塔曼·桑加特

2
@Ustaman Sangat这是一种方法,但是请记住,它从shutdownHook设置了对实例的引用,这几乎可以保证您的类永远不会被垃圾收集。换句话说,这是内存泄漏。
pieroxy

1
@pieroxy,尽管我在这里同意其他所有人关于不对任何东西使用finalize()的信息,但我不明白为什么必须从shutdown挂钩中获得引用。可能会有一个软参考。
Ustaman Sangat

25

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

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

MyClass myObj;

try {
    myObj = new MyClass();

    // ...
} finally {

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

20

请查看有效Java,第二版,第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
或使用try-with-resources
加布里埃尔·加西亚

3
假设对象的生存期在一个函数的范围内。当然,这不是OP所指的情况,基本上也不是任何人都需要的情况。考虑“缓存引擎返回值参考计数”。您希望在释放最后一个引用时释放缓存条目,但是您不知道何时释放最后一个引用。例如,finalize()可以减少引用计数。但是,如果您要求用户显式调用一个自由函数,则要求内存泄漏。通常我只是同时做这两个事情(释放功能+双重检查以完成……)……
Erik Aronesty

16

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

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

  • 如果一个对象永远不会变得不可访问,finalize()则永远不会对其进行调用。

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

  • GC可能需要一个以上的GC周期才能确定某个特定对象不可访问。(Java GC通常是“世代”的收集器...)

  • 一旦GC检测到对象不可访问和可终结,则将其放置在终结队列中。完成通常与普通GC异步进行。

(JVM规范实际上允许 JVM 永远不运行终结器……只要不回收对象使用的空间即可。通过这种方式实现的JVM将会残废/无用,但是这种行为是“允许的” )

结果是,依靠终结来完成必须在确定的时间范围内完成的事情是不明智的。完全不使用它们是“最佳实践”。应该有一种更好(即更可靠)的方法来执行您正在尝试执行的操作finalize()方法。

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


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

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

  • 该对象不是垃圾收集的,因为它仍然可以到达。
  • 该对象不是垃圾收集的,因为GC在测试完成之前不会运行。
  • 该对象由GC找到,并由GC放置在完成队列中,但是在测试完成之前未完成完成。

10

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

具体来说,当不再使用对象时,将调用finalize()。但是,当我们尝试通过创建新对象来调用它时,并不确定调用它。因此,对于把握我们创建了一个null对象c,这显然是没有前途的使用,因此我们看到物体c的敲定电话。

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或不清除b。


10
调用System.gc();并不能保证垃圾回收将真正运行。
西蒙·佛斯伯格

3
它也不能保证什么样的集合将会运行。相关,因为大多数Java GC是“世代”收集器。
斯蒂芬·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();

如你看到的。下面的输出显示了gc在类计数为36时第一次执行。

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)中,以下几点是正确的:

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

最终思想

终结方法不可靠,但只能用于一件事。您可以确保在垃圾回收之前关闭或处置了一个对象,如果正确处理了涉及生命周期结束动作的更复杂生命周期的对象,则可以实现故障保护。这是我可以想到的一个原因,因此值得对其进行覆盖。


2

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

  1. 该对象的所有引用都显式设置为null,例如object = null
  2. 在一个块内创建对象,并且一旦控件退出该块,引用就会超出作用域。
  3. 如果一个对象持有另一个对象的引用,并且将容器对象的引用设置为null,则父对象设置为null,则子对象或包含的对象将自动变为有资格进行垃圾回收的对象。
  4. 如果对象仅通过WeakHashMap具有实时引用,则可以进行垃圾回收。

如果final在缓慢的计算过程中重复使用对象的字段,并且此后将不再使用该对象,则该对象会一直保持活动状态,直到源代码最后请求该字段为止,或者JIT可以将该字段复制到临时变量,然后在计算之前放弃对象?
超级猫

@supercat:优化器可以将代码重写为首先不创建对象的形式;在这种情况下,除非同步在对象的使用和终结器之间强制执行顺序,否则它可能在构造函数完成后立即确定。
Holger

@Holger:在JIT可以看到对象创建和放弃之间发生的所有事情的情况下,我看不到JIT提早启动终结器的任何原因。真正的问题是需要执行哪些代码来确保终结器无法在特定方法中触发。在.NET中,GC.KeepAlive()除了强制GC假定它可能使用对象之外,没有执行任何其他功能,但是我知道Java中没有这样的功能。可以将volatile变量用于此目的,但仅将变量用于此目的似乎是浪费的。
超级猫

@supercat:JIT不会触发最终确定,它只是安排代码不保留引用,但是,直接将放入队列可能是有益的FinalizerReference,这样就不需要GC周期来找出没有参考。同步足以确保事前发生关系。由于完成可能(实际上是)在不同的线程中运行,因此无论如何在形式上通常都是必需的。Java 9将添加Reference.reachabilityFence……
Holger

@Holger:如果JIT优化了对象的创建,则唯一Finalize会被调用的是JIT产生的代码直接这样做了。通常只希望在单个线程中使用的对象需要同步代码吗?如果对象执行某些需要放弃的操作(例如,打开套接字连接并获得另一端资源的独占使用权),那么在代码仍在使用套接字的情况下,让终结器关闭该连接将是一场灾难。 。代码使用同步会很正常吗?
supercat

2

无法保证finalize方法。当对象有资格使用GC时,将调用此方法。在许多情况下,可能不会垃圾回收对象。


3
不正确 您的意思是当对象变得不可访问时将其终结。实际上是在实际收集方法时调用的。
斯蒂芬·C

2

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

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

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


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

当内存中充满转储对象时,gc将调用finalize方法

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


0

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

如果垃圾收集器尝试收集对象,则将调用finalize()方法。

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

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

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

请记住,它可能不会被调用,并且绝对不会被调用两次。finalize()方法可以运行零次或运行一次。

在下面的代码中,由于我们在需要运行垃圾收集器之前退出程序,因此finalize()方法在运行时不会产生任何输出。

资源


0

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

例:

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


0

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

终结器没有固定的执行时间,因为执行时间取决于Java虚拟机(JVM)。唯一的保证是,任何执行的终结器方法都将在关联对象变得不可访问之后的某个时间(在垃圾回收的第一个周期中检测到)和垃圾回收器回收关联对象的存储之前的某个时间(在垃圾回收器的第二个周期期间)执行。 。在对象变得不可访问之后,对象的终结器的执行可能会延迟任意长时间。因此,调用时间紧迫的功能,例如在对象的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();
            }
     }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.