虚引用必须搭配ReferenceQueue才能感知对象被回收,因其本身不阻止回收且get()恒返回null;JVM通过Reference Handler线程将已回收对象对应的虚引用入队,需主动poll()或remove()监听,否则无法获知回收事件。

虚引用为什么必须搭配 ReferenceQueue 才能感知对象被回收
虚引用(PhantomReference)本身不能阻止对象被回收,也不提供任何访问对象的途径——它唯一的作用就是“通知你:这东西刚被 GC 清掉了”。但 JVM 不会主动调用你的代码,所以得靠 ReferenceQueue 来被动监听。
常见错误现象:PhantomReference.get() 永远返回 null;不把虚引用注册到队列里,就永远等不到回收通知。
- 创建虚引用时,第二个参数必须是已初始化的
ReferenceQueue实例,否则等于白建 - 回收通知不是实时的:GC 后对象进入“pending”状态,由 JVM 的 Reference Handler 线程异步入队,可能有毫秒级延迟
- 必须轮询
queue.poll()或阻塞调用queue.remove()才能拿到被回收的虚引用对象(注意:不是原对象!是那个PhantomReference实例)
ReferenceQueue 的实际使用姿势:别直接 new,要配合清理逻辑
很多人以为把虚引用丢进队列就完事了,其实关键在“谁来消费队列”。JVM 只负责把虚引用对象放进去,后续资源释放、日志记录、连接关闭等,全得你自己写逻辑去处理。
使用场景:文件句柄、Socket 连接、JNI 分配的内存、自定义缓存对象等无法靠 finalize() 安全清理的资源。
立即学习“Java免费学习笔记(深入)”;
- 不要在
ReferenceQueue上做耗时操作(比如网络请求),否则会卡住 Reference Handler 线程,影响其他引用处理 - 推荐用单独线程 +
queue.remove()阻塞等待,避免空轮询浪费 CPU - 每次从队列取出
PhantomReference后,应立即执行清理,并考虑是否需要显式调用clear()(虽然虚引用本身已不可达,但保持习惯更安全)
ReferenceQueuequeue = new ReferenceQueue<>(); PhantomReference ref = new PhantomReference<>(resource, queue); // 单独线程消费 new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { PhantomReference r = (PhantomReference ) queue.remove(); cleanup(r); // 自己写的清理方法 } catch (InterruptedException e) { break; } } }).start();
和软引用、弱引用共用 ReferenceQueue 时要注意什么
同一个 ReferenceQueue 可以注册多种引用类型,但出队时类型擦除,你拿到的是 Reference>,必须手动判断或用泛型约束。
参数差异:SoftReference 和 WeakReference 构造函数也支持传 ReferenceQueue,但它们的入队时机不同:软引用在内存不足时才可能入队,弱引用在下次 GC 就入队,虚引用则在 finalize 之后(且无 finalize 方法时紧随 GC)。
- 如果混用,建议用
instanceof区分类型,或为不同类型分配独立队列,避免逻辑耦合 - 虚引用入队后,其
get()一定为null;而软/弱引用入队前仍可能通过get()拿到原对象(取决于 GC 阶段) - 别依赖队列顺序:不同引用类型的回收时机受 GC 算法、堆配置影响,没有严格先后
容易被忽略的兼容性坑:ReferenceQueue 在 Android 和老 JDK 上的行为差异
Android 8.0(Oreo)之前,ReferenceQueue 的实现不完全遵循 JVM 规范,尤其在并发消费时可能出现漏通知;JDK 6–7 中,ReferenceQueue.remove(long) 超时逻辑有 bug,某些情况下会无限等待。
性能影响:频繁创建/注册虚引用 + 队列消费,会增加 GC 的额外负担。实测在高吞吐服务中,每秒数万次虚引用注册可能让 Reference Handler 线程成为瓶颈。
- 生产环境慎用虚引用做高频资源管理,优先考虑
Cleaner(JDK 9+)或AutoCloseable显式释放 - 若必须用,避免在循环内反复 new
PhantomReference和ReferenceQueue,复用队列,引用对象可池化 - Android 开发请确认 targetSdkVersion ≥ 26,否则虚引用行为不稳定,尤其涉及 native 资源时
真正难的从来不是怎么把引用塞进队列,而是确保队列不积压、清理不遗漏、异常不静默——这些细节不会报错,但会在某个大促凌晨三点让你翻遍 GC 日志。









