PhantomReference 用于对象被垃圾回收后通知清理堆外内存,其 get() 始终返回 null,需预存资源句柄;Weak/SoftReference 不可靠,易导致 use-after-free;推荐使用 JDK9 的 Cleaner 替代手动实现。

PhantomReference 本身不能直接获取对象,它的核心用途是配合 ReferenceQueue 在对象被垃圾回收后收到通知,从而安全执行堆外内存清理等收尾工作。关键在于:它只在对象已不可达、且 GC 完成清除后才入队,此时对象实例已被回收,无法再访问,因此必须提前保存堆外资源的句柄(如 ByteBuffer 的 address、Unsafe 分配的指针等)。
为什么必须用 PhantomReference 而不是 WeakReference 或 SoftReference
WeakReference 和 SoftReference 在 GC 后可能仍持有对象引用(尤其 SoftReference 在内存充足时不会被回收),且它们的 get() 方法还能返回对象,容易误触已失效的堆外资源;而 PhantomReference 的 get() 始终返回 null,强制你放弃对对象本身的依赖,只能靠预存的元数据操作堆外内存,避免 use-after-free 错误。
典型配合 ReferenceQueue 的清理流程
- 创建 PhantomReference 时,传入待监控的对象和一个已初始化的 ReferenceQueue
- 同时,把堆外内存的地址(如 long address)、大小、释放函数(如 Unsafe.freeMemory)等关键信息,以弱引用或独立结构体形式与该 PhantomReference 关联(例如用自定义子类或 Map 映射)
- 另起一个单独线程(或复用 Cleaner 线程),循环调用 ReferenceQueue 的 remove() 或 poll(),一旦拿到入队的 PhantomReference,就取出预存的堆外资源信息并调用释放逻辑
- 注意:不能在 finalize() 中释放堆外内存——finalize 已被废弃,且执行时机不确定、可能不执行;PhantomReference + ReferenceQueue 是目前官方推荐的替代方案
一个简化但可运行的示例逻辑
假设你用 Unsafe 分配了一块堆外内存:
// 创建引用时,把 addr 封装进自定义 PhantomReference 子类
CleanerRef ref = new CleanerRef(obj, queue, addr);
// 后续在队列消费端:
while ((ref = (CleanerRef) queue.remove()) != null) {
unsafe.freeMemory(ref.address); // 安全释放
}
实际开发中更推荐使用 java.lang.ref.Cleaner
JDK9 引入的 Cleaner 是对 PhantomReference + ReferenceQueue 的封装,内部自动管理队列轮询和清理任务提交,使用更简洁、线程安全、且避免手动处理线程生命周期问题。多数场景下应优先选用 Cleaner,而非手写 PhantomReference 流程。
立即学习“Java免费学习笔记(深入)”;


















