如何确保 finalize() 始终被调用(在 Java 练习中思考)

2022-09-03 06:31:00

我正在慢慢地阅读Bruce Eckel的《Thinking in Java 4th Edition》,以下问题让我感到困惑:

使用 printize( ) 方法创建一个类,该方法用于打印消息。在 main( ) 中,创建类的一个对象。修改前面的练习,以便始终调用 finalize( )。

这是我编码的内容:

public class Horse {
    boolean inStable;
    Horse(boolean in){
        inStable = in;
    }   
    public void finalize(){
        if (!inStable) System.out.print("Error: A horse is out of its stable!");
    }
}
public class MainWindow {
    public static void main(String[] args) {
        Horse h = new Horse(false);
        h = new Horse(true);
        System.gc();
    }
}

它将创建一个布尔值设置为 的新对象。现在,在方法中,它会检查是否为 。如果是,它将打印一条消息。HorseinStablefalsefinalize()inStablefalse

很遗憾,不会打印任何消息。由于条件的计算结果为 ,我的猜测是,它一开始就没有被调用。我已经多次运行该程序,并且只看到错误消息打印了几次。我的印象是,当调用时,垃圾回收器将收集任何未引用的对象。truefinalize()System.gc()

谷歌搜索正确答案给了我这个链接,它提供了更详细,更复杂的代码。它使用我以前从未见过的方法,例如 、 、 和 。System.runFinalization()Runtime.getRuntime()System.runFinalizersOnExit()

有没有人能够让我更好地了解工作原理以及如何强制它运行,或者引导我完成解决方案代码中正在执行的操作?finalize()


答案 1

当垃圾回收器找到符合回收条件但具有 的对象时,它不会立即解除分配。垃圾回收器尝试尽快完成,因此它只是将对象添加到具有挂起终结器的对象列表中。稍后在单独的线程上调用终结器。finalizer

您可以通过在垃圾回收后调用方法 System.runFinalization 来告诉系统立即尝试运行挂起的终结器。

但是,如果要强制终结器运行,则必须自己调用它。垃圾回收器不保证将收集任何对象或调用终结器。它只会做出“最大的努力”。但是,您很少需要强制终结器在实际代码中运行。


答案 2

在玩具场景之外,通常无法确保始终对不存在“有意义”引用的对象调用 a,因为垃圾回收器无法知道哪些引用是“有意义的”。例如,一个类似 -class 的对象可能有一个“clear”方法,该方法将其计数设置为零,并使支持数组中的所有元素都有资格被将来的调用覆盖,但实际上并没有清除该支持数组中的元素。如果对象的数组大小为 50,并且大小为 23,则可能没有执行路径,代码可以通过该路径检查存储在数组的最后 27 个槽中的引用,但垃圾回收器无法知道这一点。因此,垃圾回收器永远不会调用这些槽中的对象,除非容器覆盖了这些数组槽,容器放弃了数组(可能有利于较小的数组),或者所有对容器本身的根引用都被销毁或以其他方式不复存在。finalizeArrayListAddCountfinalize

有各种方法可以鼓励系统调用任何碰巧不存在强根引用的对象(这似乎是问题的重点,并且其他答案已经涵盖了),但我认为重要的是要注意存在强根引用的对象集之间的区别, 以及代码可能感兴趣的对象集。这两个集合在很大程度上是重叠的,但每个集合可以包含不在另一个集合中的对象。当 GC 确定对象将不再存在,但存在终结器时,对象的终结器将运行;这可能与时间代码一致,也可能不一致,任何人都不再感兴趣。虽然如果能够使终结器在所有已不再感兴趣的对象上运行会很有帮助,但这通常是不可能的。finalize