Reference / ReferenceQueue / Cleaner 引用源码精读
概述
java.lang.ref 包提供软/弱/虚引用,让对象可以被 GC 回收、并在回收时收到通知——这是缓存(WeakHashMap、软引用缓存)与资源清理(Cleaner)的基石。理解 ReferenceHandler 守护线程与 pending 队列的配合,才能掌握回收通知的时序。本文基于 OpenJDK 21 源码拆解。
一、4 种引用类型
引用强度递减:
Strong(强引用) :new 出的普通引用,GC 永不回收
SoftReference :内存不足时回收(软引用缓存)
WeakReference :GC 发现只有弱引用可达即回收
PhantomReference :回收后入队,get() 永远返回 null
可达性规则:
强可达 > 软可达 > 弱可达 > 虚可达 > 不可达
GC 按最低可达级别决定对象存活java
// 四种引用用法
StrongRef obj; // 强引用
SoftReference<Object> s = new SoftReference<>(obj); // 软
WeakReference<Object> w = new WeakReference<>(obj); // 弱
ReferenceQueue<Object> q = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(obj, q); // 虚(必须带队列)二、Reference 的内部结构
java
// java.lang.ref.Reference
public abstract class Reference<T> {
private T referent; // 引用的目标对象
volatile ReferenceQueue<? super T> queue; // 关联队列(GC 后入队)
Reference<?> next; // 队列链表指针
transient private Reference<T> discovered; // pending 链表指针(GC 维护)
private static class Lock { }
private static final Lock lock = new Lock();
private static Reference<Object> pending; // GC 放置待处理引用的链表
static {
// 启动 ReferenceHandler 守护线程
ThreadGroup tg = Thread.currentThread().getThreadGroup();
Thread handler = new ReferenceHandler(tg, "Reference Handler");
handler.setPriority(MAX_PRIORITY); // 最高优先级
handler.setDaemon(true); // 守护线程
handler.start();
}
}Reference 关键状态:
referent → 被引用对象(GC 时按类型决定是否清除)
queue → 关联的 ReferenceQueue(null = 不入队)
pending → 静态链表头:GC 把"待处理"引用挂到这里
discovered→ 链表下一节点(GC 内部使用)三、ReferenceHandler 守护线程
java
private static class ReferenceHandler extends Thread {
public void run() {
// 无限循环处理 pending 链表
while (true) {
tryHandlePending(true); // 处理一个待处理引用
}
}
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) { // ① 有待处理引用
r = pending;
pending = r.discovered; // 取出链表头
r.discovered = null;
} else if (waitForNotify) { // ② 无 → 等待 GC 通知
lock.wait(); // 阻塞(GC 放入 pending 后 notify)
return retry; // 被唤醒重试
} else {
return false;
}
}
} catch (OutOfMemoryError x) { ... }
// ③ 处理:Cleaner 直接 clean,普通引用入队
if (r instanceof Cleaner) {
((Cleaner) r).clean(); // Cleaner 专用快速路径
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r); // ④ 入队(通知应用层)
return true;
}ReferenceHandler 处理流程:
GC 回收对象 → 把对应 Reference 挂到 pending 链表 → notify(lock)
守护线程被唤醒 → 取 pending →
是 Cleaner → 直接 clean()(不依赖应用队列)
否则 → enqueue 到关联的 ReferenceQueue
应用层通过 queue.poll() 或 remove() 获取被回收对象的引用四、软/弱/虚引用的回收时机
4.1 SoftReference 的 LRU 策略
java
// java.lang.ref.SoftReference
static private long clock; // 全局时钟
private long timestamp; // 最近访问时间
public T get() {
T o = super.get();
if (o != null) timestamp = clock; // 访问时更新时间戳
return o;
}SoftReference 回收规则(HotSpot):
参数:-XX:SoftRefLRUPolicyMSPerMB=1000(默认)
含义:每 1MB 堆允许软引用存活 1000ms(按最近访问)
逻辑:堆内存充足 → 软引用对象存活
堆内存紧张 → 按"上次访问时间"淘汰最久未用的软引用
适用:内存缓存(大对象缓存、图片缓存)4.2 WeakReference 的即时回收
java
// 弱引用:GC 只要发现对象仅被弱引用可达 → 直接回收
// (无论堆内存是否充足,每次 GC 都可能清除)
// 典型应用:WeakHashMap(键为弱引用)
// 键不再被外部引用 → GC 自动回收 → 条目随之清除4.3 PhantomReference 的不可复活
java
// java.lang.ref.PhantomReference
public T get() { return null; } // 始终返回 null
// 特殊字段:phantomReferent(虚引用保留的内部引用)PhantomReference 特性:
get() 永远 null → 对象"无法复活"(无法从虚引用恢复引用)
必须在构造时传入 ReferenceQueue → 回收后入队
用途:对象回收后的资源清理(Cleaner 的实现基础)
时序:对象 finalize 之后、内存释放之前入队 → 此时资源还可访问五、Cleaner 清理机制
5.1 sun.misc.Cleaner vs java.lang.ref.Cleaner
两代 Cleaner:
sun.misc.Cleaner(JDK 8 及以前,DirectByteBuffer 用)
java.lang.ref.Cleaner(JDK 9+,正式 API)
共同原理:PhantomReference + 关联清理 Runnable
共同目标:替代 finalize(),回收时确定性执行清理5.2 java.lang.ref.Cleaner 的实现
java
// java.lang.ref.Cleaner
public final class Cleaner implements Runnable {
final CleanerImpl impl; // 内部实现
public static Cleaner create() {
return create(null);
}
public static Cleaner create(ThreadFactory factory) {
CleanerImpl impl = new CleanerImpl(); // 内含 CleanerImpl.Queue(引用队列)
impl.start(factory); // 启动清理线程(ReferenceQueue 消费者)
return new Cleaner(impl);
}
// 注册清理动作
public Cleanable register(Object obj, Runnable action) {
return impl.add(obj, action); // 包装成 PhantomReference + 动作
}
public void run() { impl.clean(); } // 清理所有已入队项
}java
// CleanerImpl 内部(引用队列消费者线程)
class CleanerImpl {
final CleanerImpl.Queue queue = new CleanerImpl.Queue(); // 引用队列
void start(ThreadFactory factory) {
// 启动"清理线程":阻塞在 queue.remove() 等待入队的虚引用
Thread t = factory.newThread(new Runnable() {
public void run() {
while (!queue.isEmpty() || !isShutdown) {
CleanerImpl.PhantomCleanable<?> ref =
(PhantomCleanable<?>) queue.remove(); // 阻塞取
ref.clean(); // 执行清理 Runnable
}
}
});
t.setDaemon(true);
t.start();
}
}Cleaner 完整链路(以 DirectByteBuffer 为例):
创建 DirectByteBuffer → cleaner = Cleaner.create().register(buffer, deallocator)
① buffer 被 GC 回收 → 包装的 PhantomReference 进入 pending → ReferenceHandler 入队
② CleanerImpl 清理线程 queue.remove() 拿到虚引用
③ 调用 ref.clean() → 执行注册的 deallocator(unsafe.freeMemory 释放堆外内存)
→ 确定性清理,无需显式 close/finalize六、实现要点
Reference / Cleaner 核心:
四种引用:Strong/Soft/Weak/Phantom,可达性递减
ReferenceQueue:GC 回收后入队通知应用层
ReferenceHandler:守护线程(最高优先级)循环处理 pending 链表
tryHandlePending:Cleaner 直接 clean / 普通引用 enqueue
SoftReference:SoftRefLRUPolicyMSPerMB 按 LRU 淘汰(内存紧张时)
WeakReference:GC 即回收(WeakHashMap 基础)
PhantomReference:get()=null 不可复活,必须带队列
Cleaner:PhantomReference + 注册 Runnable,清理线程执行
常见陷阱:
强引用残留在集合 → GC 无法回收(引用缓存需及时清理)
ReferenceQueue 不及时 poll → 队列堆积(引用对象泄漏)
Cleaner 清理线程是守护线程 → JVM 退出不保证清理完成
虚引用入队不代表对象内存已释放(只是已不可达)
finalize 已被废弃 → 清理统一用 Cleaner