finalizable 对象如何至少需要 2 个垃圾收集周期才能被回收?

How finalizable objects takes at least 2 garbage collection cycles before it can be reclaimed?

我正在阅读 this 文章,但我无法真正理解 可终结对象(覆盖 finalize 方法的对象)在之前至少需要 2 个 GC 周期可以回收.

It takes at least two garbage collection cycles (in the best case) before a finalizeable object can be reclaimed.

谁也能详细解释一下一个finalizable对象怎么可能需要一个以上的GC周期来回收?

我的逻辑论点是,当我们覆盖 finalize 方法时,运行时将不得不向垃圾收集器注册这个对象(以便 GC 可以调用这个对象的 finalize,这让我认为 GC将引用所有可终结的对象)。为此,GC 必须保持对可终结对象的强引用。如果是这样,那么这个对象最初是如何成为 GC 回收的候选对象的呢?这个理论让我自相矛盾。

PS:我知道覆盖 finalize 不是推荐的方法,并且自 Java 9.

以来不推荐使用此方法

你说得对,垃圾收集器需要对可终结对象的引用。当然,在确定对象在终结之前是否仍然可达时,一定不要考虑这个特定的引用。这意味着对垃圾收集器引用的性质有特殊的了解。

当垃圾收集器确定一个对象有资格进行终结时,终结器将 运行,这意味着该对象再次变得强可达,至少只要执行终结器。完成后,对象必须再次变得不可访问,并且必须检测到这一点,然后才能回收对象的内存。这就是为什么它至少需要两个垃圾收集周期。

在广泛使用的 Hotspot/OpenJDK 环境中(也可能在 IBM 的 JVM 中),这是通过创建一个特殊的、非 public subclass 的实例来实现的Reference, a Finalizer,就在创建对象时,其 class 具有非平凡的 finalize() 方法。与弱引用和软引用一样,当不存在对引用对象的强引用时,这些引用会被垃圾收集器排入队列,但它们不会被清除,因此终结器线程可以读取对象,使其再次强可达以进行终结。此时,Finalizer 被清除,但也不再被引用,所以它会像普通对象一样被收集,所以下次引用对象变得不可访问时,不再有对它的特殊引用。

对于 class 具有“普通终结器”的对象,即 java.lang.Object 继承的 finalize() 方法或空的 finalize() 方法,JVM 将采用捷径而不是首先创建 Finalizer 实例,所以你可以说,这些占所有对象大部分的对象的行为就好像它们的终结器已经 运行,从开始。

虽然你得到了答案(绝对正确),但我想在这里添加一个小的附录。通常,引用有两种类型:strongweak。弱引用是 WeakReference/SoftReference/PhantomReferenceFinalizer(s)。

当某个GC循环遍历堆图并看到其中一个weak references时,它以一种特殊的方式对待它。当它第一次遇到死的终结器引用时(让我们认为这是第一个 GC 周期),它必须复活实例。 finalize是一个实例方法,它需要一个actual实例被调用。所以 GC 首先看到这个 Object 已经死了,只是在片刻之后将其复活,以便能够对其调用 finalize 。一旦它在其上调用了该方法,它就标记了它已经被调用的事实;所以当下一个循环发生时,它实际上可以被GC-ed。

将此称为 second GC 是不正确的。

例如 G1GC 会部分清理堆(新堆和混合堆),因此它甚至可能不会在 next 循环中捕获此引用。就这么简单,它可能不在它的雷达范围内。

其他 GC,如 Shenandoah,具有控制在哪个迭代中处理这些特殊引用的标志(ShenandoahRefProcFrequency,默认为 5)。

所以确实需要 two 个周期,但它们不一定是后续的。