PhantomReference仅用于对象被彻底回收后的通知,其get()恒返回null,必须配合ReferenceQueue使用,且对象不能有任何非虚引用残留;Java 9+推荐用Cleaner替代。

PhantomReference 不能用来取对象,只能等它被回收时收个“通知”
虚引用的 get() 永远返回 null,这点和 SoftReference、WeakReference 完全不同。它不参与对象的可达性判定,只在对象**已经从内存中彻底移除后**,由 GC 放入关联的 ReferenceQueue —— 这是它唯一能“触发”的时机。
所以别想着用 phantomRef.get() 拿回对象,也别把它当缓存或延迟加载工具。它的存在意义就是:你得自己配一个 ReferenceQueue,然后轮询或监听这个队列,一旦看到有 PhantomReference 出来,说明对应对象刚被 GC 清掉,可以安全执行清理逻辑了。
- 必须显式传入
ReferenceQueue构造,否则等于白建 - 不能靠
isEnqueued()判断是否该清理——要轮询queue.poll()或用守护线程持续remove() - 对象进入
ReferenceQueue时,其finalize()已执行完毕(如果有的话),且所有字段都不可访问
堆外内存自动回收不是 PhantomReference 自动做的,而是你用它“手动搭钩子”
JVM 不管 ByteBuffer.allocateDirect() 分配的堆外内存,GC 只负责堆内对象。所谓“自动”,其实是 JDK 内部用 PhantomReference + Cleaner(Java 9+)或 sun.misc.Cleaner(旧版)搭了一层间接回调。你写的代码里几乎不会直接 new PhantomReference 去管 DirectBuffer,那是 JDK 底层干的事。
如果你真要自己管理堆外资源(比如自定义 native buffer、文件句柄、GPU 显存),才需要模仿这套模式:分配资源 → 创建 PhantomReference 关联 ReferenceQueue → 启一个后台线程消费队列 → 在消费时调用 free() 或 close()。
立即学习“Java免费学习笔记(深入)”;
- 别在
ReferenceQueue消费逻辑里做耗时操作(如网络请求、磁盘写入),会卡住整个 Cleaner 线程 - Java 9+ 优先用
CleanerAPI,它封装了PhantomReference的样板逻辑,更安全 -
Cleaner的清理动作是异步的,无法保证及时性;极端情况下(如 OOM 前 GC 频繁但没触发 full GC),堆外内存可能堆积
和 WeakReference / SoftReference 混用会失效,因为它们触发时机完全不同
有人想“保险一点”,给同一个对象同时套上 WeakReference 和 PhantomReference,结果发现 PhantomReference 死活不进队列。原因很简单:WeakReference 的存在会让对象在 GC 时被“弱可达”,从而跳过 finalization 阶段,而 PhantomReference 必须等对象走完 finalize(如果启用)并真正被清除后才能入队。
换句话说:只要还有任何非虚引用(包括 WeakReference、SoftReference、强引用)指向该对象,PhantomReference 就永远不会被 enqueued。
- 确保目标对象除了
PhantomReference外,没有任何其他引用残留(包括 static 字段、集合容器、监听器列表) - 调试时可用
System.gc()+Thread.sleep()强制触发,但生产环境绝不能依赖 - 用 VisualVM 或 JFR 观察
ReferenceQueue是否有元素出队,比打日志更可靠
真实项目里,90% 的场景该用 Cleaner 而不是手写 PhantomReference
Java 9 引入的 Cleaner 是对 PhantomReference 的封装,隐藏了队列轮询、线程管理、异常吞没等细节。你只需要传一个清理动作(Runnable),JDK 自带的守护线程就会在合适时机调用它。
示例:
Cleaner cleaner = Cleaner.create();
cleaner.register(myResource, () -> {
// 这里写释放 native buffer / close fd 的逻辑
freeNativeBuffer(ptr);
});
这比自己 new PhantomReference、启线程、处理中断、重试失败更轻量,也更符合 JVM 当前的设计意图。
-
Cleaner默认使用 daemon 线程,进程退出时不保证执行清理动作;如有强依赖,需额外加 shutdown hook - 不要在清理动作里抛异常,
Cleaner会静默吞掉,且不会重试 - 多个资源共用一个
Cleaner实例即可,不用每个都 new 一个
虚引用真正的价值不在“自动”,而在“可控的、确定性的清理时机”。但它极其容易用错——稍有引用残留就静默失效,又没有报错提示。所以除非你在写类似 Netty 的 PooledByteBufAllocator 这种底层资源池,否则老实用 Cleaner,别碰裸的 PhantomReference。










