PhantomReference 是 JVM 堆外内存回收的关键基础设施,它不直接释放资源,仅在对象被彻底回收后提供唯一通知时机;其通过 DirectByteBuffer → Cleaner → PhantomReference → ReferenceQueue 链路,配合 Cleaner 的守护线程监听 ReferenceQueue 并执行 clean() 释放 native 内存,确保堆外资源在对象不可达且 finalize 完成后精准释放。

PhantomReference 在 JDK 源码中不直接暴露为“堆外内存回收工具”,而是作为底层清理机制的基础设施——它本身不做释放,只提供对象被彻底回收后的**唯一通知时机**。分析它的关键,是顺着 DirectByteBuffer → Cleaner → PhantomReference → ReferenceQueue 这条链路,看清 JVM 如何用虚引用搭起“堆外资源与 GC 生命周期”的桥梁。
从 DirectByteBuffer 构造看 Cleaner 的注入
在 java.nio.DirectByteBuffer 的构造方法中(JDK 17+ 位于 src/java.base/share/classes/java/nio/DirectByteBuffer.java),你会看到:
- 调用
Bits.reserveMemory(size, cap)预占堆外内存额度; - 通过
Unsafe.allocateMemory(size)实际分配 native 内存; - 最关键的一行:
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
这里 Cleaner.create() 并非简单包装,而是内部创建了一个 PhantomReference<Object> 子类实例(Cleaner 继承自 PhantomReference),并将当前 DirectByteBuffer 实例和一个私有 ReferenceQueue 传入。这意味着:每个 DirectByteBuffer 对象一旦脱离强引用,就自动注册了一个“死亡哨兵”。
Cleaner 是 PhantomReference 的封装增强版
sun.misc.Cleaner(旧版)或 jdk.internal.ref.Cleaner(Java 9+)本质上是 PhantomReference 的子类,但做了三件事:
- 构造时强制绑定一个静态共享的
ReferenceQueue(避免每个 Cleaner 自建队列); - 自带一个
clean()方法,封装了用户提供的清理逻辑(如Deallocator中的unsafe.freeMemory()); - 启动一个守护线程(
Cleaner.FinalizerThread或Cleaner.CleanerThread),持续调用queue.remove()监听入队信号。
你不会在业务代码里 new PhantomReference,但你在 new DirectByteBuffer 时,已经悄悄触发了整套虚引用监听流程。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
ReferenceQueue 的消费逻辑决定回收是否及时
在 Cleaner 的守护线程源码中(例如 jdk.internal.ref.Cleaner.java),核心循环是:
while (true) { Cleaner cl = (Cleaner) queue.remove(); cl.clean(); }- 注意用的是
remove()而非poll():阻塞等待,确保不漏掉任何回收信号; -
cl.clean()执行的是用户传入的Runnable(如Deallocator.run()),最终调用Unsafe.freeMemory(address)释放 native 内存。
这个线程不处理异常、不重试、不记录日志——设计上就是轻量、可靠、一次性的清理钩子。如果 clean() 抛异常,该 Cleaner 就失效,但不会影响其他 Cleaner。
为什么不用 WeakReference 或 SoftReference?
源码层面能清晰看出取舍:
-
WeakReference入队时机太早:GC 发现弱可达即入队,此时对象可能还在 finalizer 队列排队,字段仍可访问,不适合做“已终结”信号; -
SoftReference入队不可控:受内存压力驱动,可能长期存活,导致堆外内存迟迟不释放; -
PhantomReference是唯一满足“对象已不可达、finalize 已执行、所有字段不可访问、仅剩虚引用残留”这四个条件后才入队的引用类型——正是堆外资源安全释放所需的精确窗口。
这也是为什么 Cleaner 显式继承 PhantomReference,而非其他引用类型。

















