光之使者 发表于 2024-6-17 20:32:27

FinalReference 如何使 GC 过程变得拖拖拉拉

本文基于 OpenJDK17 进行讨论,垃圾回收器为 ZGC。
提示: 为了方便各人索引,特将在上篇文章 《以 ZGC 为例,谈一谈 JVM 是如何实现 Reference 语义的》 中讨论的众多主题独立出来。
FinalReference 对于我们来说是一种比较陌生的 Reference 范例,因为我们好像在各大中间件以及 JDK 中并没有见过它的应用场景,事实上,FinalReference 被设计出来的目标也不是给我们用的,而是给 JVM 用的,它和 Java 对象的 finalize() 方法执行机制有关。
public class Object {
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
}我们看到 finalize()方法在 OpenJDK9 中已经被标记为 @Deprecated 了,并不保举利用。笔者实在一开始也并不想提及它,但是思来想去,本文是主要先容各类 Refernce 语义实现的,前面笔者已经非常具体的先容了 SoftReference,WeakReference,PhantomReference 在 JVM 中的实现。
在文章的最后何不利用这个 FinalReference 将前面先容的内容再次为各人串联一遍,加深一下各人对 Reference 整个处置惩罚链路的明白,基于这个目标,才有了本末节的内容。但笔者的本意并不是为了让各人利用它。
下面我们还是按照老例子,继续从 JDK 以及 JVM 这两个视角全方位的先容一下 FinalReference 的实现机制,并为各人表明一下这个 FinalReference 如何使整个 GC 过程变得拖拖拉拉,磨磨唧唧~~~
1. 从 JDK 视角看 FinalReference

https://img2024.cnblogs.com/blog/2907560/202406/2907560-20240617205434411-268076648.png
FinalReference 本质上来说它也是一个 Reference,所以它的基本语义和 WeakReference 保持同等,JVM 在 GC 阶段对它的整体处置惩罚流程和 WeakReference 也是大致一样的。
唯逐一点不同的是,由于 FinalReference 是和被它引用的 referent 对象的 finalize() 执行有关,当一个普通的 Java 对象在整个 JVM 堆中只有 FinalReference 引用它的时候,按照 WeakReference 的基础语义来讲,这个 Java 对象就要被回收了。
但是在这个 Java 对象被回收之前,JVM 需要保证它的 finalize()被执行到,所以 FinalReference 会再次将这个 Java 对象重新标记为 alive,也就是在 GC 阶段重新复活这个 Java 对象。
后面的流程就和其他 Reference 一样了,FinalReference 也会被 JVM 加入到 _reference_pending_list 链表中,ReferenceHandler 线程被叫醒,随后将这个 FinalReference 从 _reference_pending_list 上摘下,并加入到与其关联的 ReferenceQueue 中,这个流程就是我们第三末节主要讨论的内容,各人还记得吗 ?
https://img2024.cnblogs.com/blog/2907560/202406/2907560-20240617205448256-2139573901.png
和 Cleaner 不同的是,对于 FinalReference 来说,在 JDK 中另有一个叫做 FinalizerThread 线程来专门处置惩罚它,FinalizerThread 线程会不停的从与 FinalReference 关联的 ReferenceQueue 中,将所有需要被处置惩罚的 FinalReference 摘下,然后挨个执行被它所引用的 referent 对象的finalize() 方法。
随后在下一轮的 GC 中,FinalReference 对象以及它引用的 referent 对象才会被 GC 回收掉。
以上就是 FinalReference 被 JVM 处置惩罚的整个生命周期,下面让我们先回到最初的起点,这个 FinalReference 是怎么和一个 Java 对象关联起来的呢 ?
我们知道 FinalReference 是和 Java 对象的finalize() 方法执行有关的,如果一个 Java 类没有重写 finalize() 方法,那么在创建这个 Java 类的实例对象的时候将不会和这个 FinalReference 有任何的瓜葛,它就是一个普通的 Java 对象。
但是如何一个 Java 类重写了 finalize() 方法 ,那么在创建这个 Java 类的实例对象的时候, JVM 就会将一个 FinalReference 实例和这个 Java 对象关联起来。
instanceOop InstanceKlass::allocate_instance(TRAPS) {
// 判断这个类是否重写了 finalize() 方法
bool has_finalizer_flag = has_finalizer();
instanceOop i;
// 创建实例对象
i = (instanceOop)Universe::heap()->obj_allocate(this, size, CHECK_NULL);
// 如果该对象重写了finalize() 方法
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
    // JVM 这里就会调用 Finalizer 类的静态方法 register
    // 将这个 Java 对象与 FinalReference 关联起来
    i = register_finalizer(i, CHECK_NULL);
}
return i;
}我们看到,在 JVM 创建对象实例的时候,会首先通过 has_finalizer() 方法判定这个 Java 类有没有重写 finalize() 方法,如果重写了就会调用 register_finalizer 方法,JVM 最终会调用 JDK 中的 Finalizer 类的静态方法 register。
final class Finalizer extends FinalReference<Object> {
    static void register(Object finalizee) {
      new Finalizer(finalizee);
    }
}在这里 JVM 会将刚刚创建出来的普通 Java 对象 —— finalizee,与一个 Finalizer 对象关联起来, Finalizer 对象的范例正是 FinalReference 。这里我们可以看到,当一个 Java 类重写了 finalize() 方法的时候,每当创建一个该类的实例对象,JVM 就会自动创建一个对应的 Finalizer 对象。
Finalizer 的整体设计和之前先容的 Cleaner 非常相似,不同的是 Cleaner 是一个 PhantomReference,而 Finalizer 是一个 FinalReference。
它们都有一个 ReferenceQueue,只不过 Cleaner 中的那个基本没啥用,但是 Finalizer 中的这个 ReferenceQueue 却有非常紧张的作用。
它们内部都有一个双向链表,内里包含了 JVM 堆中所有的 Finalizer 对象,用来确保这些 Finalizer 在执行 finalizee 对象的 finalize() 方法之前不会被 GC 回收掉。
final class Finalizer extends FinalReference<Object> {

    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();

    // 双向链表,保存 JVM 堆中所有的 Finalizer 对象,防止 Finalizer 被 GC 掉
    private static Finalizer unfinalized = null;

    private Finalizer next, prev;

    private Finalizer(Object finalizee) {
      super(finalizee, queue);
      // push onto unfinalized
      synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
      }
    }
}在创建 Finalizer 对象的时候,首先会调用父类方法,将被引用的 Java 对象以及 ReferenceQueue 关联注册到 FinalReference 中。
    Reference(T referent, ReferenceQueue
页: [1]
查看完整版本: FinalReference 如何使 GC 过程变得拖拖拉拉