Fundamentals 20 min read

How Does Java’s FinalReference Keep Objects Alive During GC?

This article explains how the JVM creates and manages FinalReference objects, how they interact with the finalize() method, and how the ZGC and other GC phases discover, revive, and eventually reclaim objects, detailing the roles of ReferenceHandler, FinalizerThread, and related internal data structures.

Bin's Tech Cabin
Bin's Tech Cabin
Bin's Tech Cabin
How Does Java’s FinalReference Keep Objects Alive During 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 ,并不推荐使用。本文主要介绍各类 Reference 语义实现,前面已经详细介绍了 SoftReference、WeakReference、PhantomReference 在 JVM 中的实现。

下面我们继续从 JDK 以及 JVM 两个视角全方位介绍 FinalReference 的实现机制,并解释它如何使整个 GC 过程变得拖拖拉拉。

1. 从 JDK 视角看 FinalReference

FinalReference 本质上也是一个 Reference,基本语义和 WeakReference 保持一致,JVM 在 GC 阶段对它的整体处理流程和 WeakReference 大致相同。

唯一不同的是,FinalReference 与被它引用的 referent 对象的 finalize() 执行有关。当一个普通 Java 对象在堆中只有 FinalReference 引用时,按照 WeakReference 的语义,这个对象会被回收。

但在对象被回收之前,JVM 需要保证它的 finalize() 被执行,所以 FinalReference 会再次将该对象标记为 alive,即在 GC 阶段重新复活对象。

随后 FinalReference 会被加入到 _reference_pending_list 链表,ReferenceHandler 线程被唤醒,将其摘下并加入关联的 ReferenceQueue,这一流程在后文第三小节详细讨论。

与 Cleaner 不同,FinalReference 在 JDK 中还有一个叫做 FinalizerThread 的系统线程专门处理它。该线程不断从与 FinalReference 关联的 ReferenceQueue 中取出对象,执行其 referent 的 finalize() 方法。

在下一轮 GC 中,FinalReference 对象以及它引用的 referent 对象才会被真正回收。

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() 判断类是否重写 finalize() ,若是则调用 register_finalizer ,最终在 JDK 中的 Finalizer 类的 register 方法创建一个 Finalizer 对象(即 FinalReference 的子类),并将其与对象关联。

final class Finalizer extends FinalReference<Object> {
    static void register(Object finalizee) {
        new Finalizer(finalizee);
    }
}

当一个类重写了 finalize() ,每当创建该类的实例时,JVM 会自动创建对应的 Finalizer 对象。

Finalizer 的整体设计与 Cleaner 相似,但 Cleaner 是 PhantomReference,而 Finalizer 是 FinalReference。它们都有一个 ReferenceQueue,Cleaner 中的基本无用,而 Finalizer 的 ReferenceQueue 在后续处理阶段非常关键。

final class Finalizer extends FinalReference<Object> {
    private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
    private static Finalizer unfinalized = null;
    private Finalizer next, prev;
    private Finalizer(Object finalizee) {
        super(finalizee, queue);
        synchronized (lock) {
            if (unfinalized != null) {
                this.next = unfinalized;
                unfinalized.prev = this;
            }
            unfinalized = this;
        }
    }
}

创建 Finalizer 时会将其插入双向链表 unfinalized ,防止在执行 finalize() 之前被 GC 回收。

Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

FinalizerThread 在初始化时创建,并设置为系统线程,优先级为 Thread.MAX_PRIORITY - 2 ,比普通业务线程高但低于 ReferenceHandler。

final class FinalizerThread extends Thread {
    static {
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent());
        Thread finalizer = new FinalizerThread(tg);
        finalizer.setPriority(Thread.MAX_PRIORITY - 2);
        finalizer.setDaemon(true);
        finalizer.start();
    }
}

ReferenceHandler 线程的优先级最高,随后是 FinalizerThread,最后是普通业务线程。

public abstract class Reference<T> {
    static {
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();
    }
}

在 ZGC 的 Concurrent Mark 阶段,GC 线程遍历到 FinalReference 时,会通过 should_discover 判断是否应将其插入 _discovered_list ,条件包括 referent 没有强引用或软引用且未被标记为 inactive。

bool ZReferenceProcessor::should_discover(oop reference, ReferenceType type) const {
    volatile oop* const referent_addr = reference_referent_addr(reference);
    const oop referent = ZBarrier::weak_load_barrier_on_oop_field(referent_addr);
    if (is_inactive(reference, referent, type)) return false;
    if (is_strongly_live(referent)) return false;
    if (is_softly_live(reference, type)) return false;
    return true;
}

若满足条件,JVM 会在 discover 方法中对 referent 进行标记,使其在回收阶段保持可达(finalizable),并将 Reference 加入 _discovered_list

void ZReferenceProcessor::discover(oop reference, ReferenceType type) {
    if (type == REF_FINAL) {
        volatile oop* const referent_addr = reference_referent_addr(reference);
        ZBarrier::mark_barrier_on_oop_field(referent_addr, true /* finalizable */);
    }
    // Add reference to discovered list
    assert(reference_discovered(reference) == NULL, "Already discovered");
    oop* const list = _discovered_list.addr();
    reference_set_discovered(reference, *list);
    *list = reference;
}

随后在 ZGC 的 Concurrent Process Non-Strong References 阶段,所有在 _discovered_list 中的 Reference(包括 FinalReference)会被转移到 _reference_pending_list ,并唤醒 ReferenceHandler 线程处理。

ReferenceHandler 将 FinalReference 加入其对应的 ReferenceQueue,FinalizerThread 随后被唤醒,执行 runFinalizer ,最终调用 referent 对象的 finalize() 方法。

private void runFinalizer(JavaLangAccess jla) {
    synchronized (lock) {
        if (this.next == this) return; // already finalized
        // remove from unfinalized list
        if (unfinalized == this) unfinalized = this.next;
        else this.prev.next = this.next;
        if (this.next != null) this.next.prev = this.prev;
        this.prev = null;
        this.next = this; // mark as finalized
    }
    try {
        Object finalizee = this.get();
        if (!(finalizee instanceof java.lang.Enum)) {
            jla.invokeFinalize(finalizee);
        }
    } catch (Throwable x) { }
    super.clear();
}

runFinalizer 完成后,下一轮 GC 时,FinalReference 与其 referent 对象将一起被回收。

2. 从 JVM 视角看 FinalReference

在 ZGC 的 Concurrent Mark 阶段,GC 线程会判断 FinalReference 是否应被发现并复活其 referent,对其进行标记为 finalizable,然后加入 _discovered_list

随后 ReferenceHandler 将其移动到 _reference_pending_list ,最终由 FinalizerThread 执行 finalize() ,完成后在下一轮 GC 中被回收。

总结

只要 Java 类重写了 finalize() 方法,当其实例可以被回收时,JVM 必定会调用该方法。调用时机取决于 FinalizerThread 的调度,而由于 FinalReference 的存在,被回收的对象会在 GC 过程中被复活,直至 finalize() 执行完毕后才会在下一轮 GC 中真正被回收。

如果 finalize() 执行时间过长或 FinalizerThread 未被及时调度,可能导致多轮 GC 期间大量 Finalizer 对象残留在堆中。

JavaJVMGarbage CollectionzgcFinalReferenceFinalizerThreadReferenceHandler
Bin's Tech Cabin
Written by

Bin's Tech Cabin

Original articles dissecting source code and sharing personal tech insights. A modest space for serious discussion, free from noise and bureaucracy.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.