首页 文章

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

提问于
浏览
285

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

17 回答

  • 22

    Java finalize() 方法不是析构函数,不应该用于处理应用程序所依赖的逻辑 . Java规范声明无法保证在应用程序的实时时间内完全调用 finalize 方法 .

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

    MyClass myObj;
    
    try {
        myObj = new MyClass();
    
        // ...
    } finally {
    
        if (null != myObj) {
            myObj.cleanup();
        }
    }
    
  • 16

    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在 class 计数为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
    
  • 2

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

  • 5

    Java允许对象实现一个名为finalize()的方法,该方法可能被调用 . 如果垃圾回收器尝试收集对象,则调用finalize()方法 . 如果垃圾收集器未运行,则不会调用该方法 . 如果垃圾收集器无法收集对象并尝试再次运行它,则不会在第二次调用该方法 . 在实践中,您极不可能在实际项目中使用它 . 请记住,它可能不会被调用,它肯定不会被调用两次 . finalize()方法可以运行零次或一次 . 在下面的代码中,finalize()方法在运行它时不产生输出,因为程序在需要运行垃圾收集器之前退出 .

    Source

  • 2

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

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

    使用protected关键字,可以防止对类外的代码访问finalize() . 重要的是要理解finalize()就在垃圾收集之前调用 . 例如,当对象离开范围时,不会调用它 . 这意味着您无法知道何时或是否将执行finalize() . 因此,程序必须提供其他方法来释放对象使用的系统资源或其他资源 . You should not rely on finalize() for normal running of the program.

  • 0

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

    例:

    如果程序在垃圾收集器发生之前结束,则 finalize() 将不会执行 . 因此,它应该用作备份程序,以确保正确处理其他资源或特殊用途应用程序,而不是程序在正常操作中使用的方法 .

  • 0

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

    • Finalize不会立即调用Finalizer(GC部分)单独拥有该参考文件

    • 默认垃圾收集器池无法访问的对象

    • Finalize被批量调用,指向垃圾收集器释放资源的某个阶段的实现细节 .

    • 调用System.gc()通常不会导致对象被更频繁地终结,只会导致Finalizer更快地意识到无法访问的对象

    • 创建线程转储几乎总是导致在执行堆转储或其他内部机制期间由于高堆开销而触发终结器

    • 终结接缝受内存要求(释放更多内存)或被标记为最终确定增长某个内部限制的对象列表的约束 . 因此,如果您有很多对象被最终确定,那么最终确定阶段将被触发更多与少数人相比,经常和更早

    • 有些情况下,System.gc()会直接触发终结,但前提是该引用是本地和短期生活 . 这可能与代相关 .

    Final Thought

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

  • 4

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

    • 该对象的所有引用都明确设置为null,例如object = null

    • 对象在块内创建,一旦控制退出该块,引用就会超出范围 .

    • 父对象设置为null,如果对象保存另一个对象的引用,并且当您将容器对象的引用设置为null时,子对象或包含对象将自动符合垃圾回收的条件 .

    • 如果一个对象只有WeakHashMap的实时引用,它将有资格进行垃圾回收 .

  • 0

    当垃圾收集确定没有对该对象的更多引用时,它由对象上的垃圾收集器调用 .

    子类重写finalize方法以处置系统资源或执行其他清理 .

    finalize的一般 Contract 是,当Java虚拟机确定不再有任何方法可以被任何尚未死亡的线程访问时,它被调用,除非采取了一个动作的结果通过最终确定一些其他准备完成的对象或类 .

    finalize方法可以采取任何操作,包括使该对象再次可用于其他线程 .

    然而,finalize的通常目的是在对象被不可撤销地丢弃之前执行清理操作 . 例如,表示输入/输出连接的对象的finalize方法可能会执行显式I / O事务,以在永久丢弃对象之前断开连接 .

    Java编程语言不保证哪个线程将为任何给定对象调用finalize方法 . 但是,可以保证,调用finalize时,调用finalize的线程不会持有任何用户可见的同步锁 . 如果finalize方法抛出未捕获的异常,则忽略该异常并终止该对象的终止 .

    在为对象调用finalize方法之后,在Java虚拟机再次确定不再有任何方法可以通过任何尚未死亡的线程访问此对象(包括可能的操作)之前,不会采取进一步操作通过准备完成的其他对象或类,此时可以丢弃该对象 .

    对于任何给定对象,Java虚拟机永远不会多次调用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();
            }
        }
    }
    

    当内存被dump对象重载时,gc将调用finalize方法

    运行并查看控制台,在那里你没有找到频繁调用的finalize方法,当内存过载时,将调用finalize方法 .

  • 346

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

    具体来说,当对象不再使用时,将调用finalize() . 但是当我们试图通过创建新对象来调用它时,它的调用并不确定 . 所以为了确定我们创建一个 null 对象 c ,显然没有将来使用,因此我们看到对象 c 的最终调用 .

    Example

    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.");
                }
            }
        }
    }
    

    Output

    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.
    

    Note - 即使在打印到70之后并且在程序中没有使用对象b之后,由于"Called finalize method in class Bike..."未被打印,因此不确定b是否被JVM清除 .

  • 9

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

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

    受保护的虚空finalize()抛出Throwable {}
    每个类都继承java.lang.Object中的finalize()方法 . 当垃圾收集器确定不再存在对该对象的引用时,该方法被调用.Object finalize方法不执行任何操作,但它可能被任何类重写,通常它应该被覆盖被覆盖以清理非Java资源,即如果覆盖finalize()则关闭文件,使用try-catch-finally语句并始终调用super.finalize()是一种很好的编程习惯 . 这是一种安全措施,可确保您不会无意中错过关闭调用类的对象使用的资源protected void finalize()throws Throwable {
    尝试{
    关(); //关闭打开的文件
    } finally {
    super.finalize();
    }
    }
    在垃圾收集期间由finalize()抛出的任何异常都会停止终结但是否则会被忽略finalize()永远不会在任何对象上运行多次

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

    您还可以查看这篇文章:

  • 2

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

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

    • 如果对象永远不可达,则永远不会在其上调用 finalize() .

    • 如果GC未运行,则可能永远不会调用 finalize() . (通常情况下,只有当JVM确定可能存在足够的垃圾才能使其值得时,GC才会运行 . )

    • 在GC确定无法访问特定对象之前,可能需要多个GC周期 . (Java GCs通常是“世代”收藏家...)

    • 一旦GC检测到某个对象无法访问并可终结,它就会置于终结队列中 . 终结通常与普通GC异步发生 .

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

    结果是依靠最终化来做必须在确定的时间范围内完成的事情是不明智的 . 根本不使用它们是"best practice" . 应该有更好的(即更可靠的)方法来做你想要在 finalize() 方法中做的任何事情 .

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


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

    很难说,但有一些可能性:

    • 该对象未被垃圾回收,因为它仍然可以访问 .

    • 对象未进行垃圾回收,因为GC在测试完成之前未运行 .

    • 该对象由GC找到并由GC放置在终结队列中,但在测试完成之前未完成终止 .

  • 18

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

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

    当垃圾收集确定没有对该对象的更多引用时,由对象上的垃圾收集器调用 .

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

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

  • 241

    查看Effective Java,第2版第27页. Item 7: Avoid finalizers

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

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

    // try-finally块保证终止方法的执行
    Foo foo = new Foo(...);
    尝试{
    //做foo必须做的事情
    ...
    } finally {
    foo.terminate(); //显式终止方法
    }

  • 67

    当对象即将收集垃圾时,将调用 finalize 方法 . 这可以在它有资格进行垃圾收集之后的任何时间 .

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

    有一些方法可以告诉JVM在它没有的对象上运行 finalize 't called on yet, but using them isn'也是一个好主意(该方法的保证也不是很强) .

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

相关问题