referencequeue 是 jvm 在对象被回收后自动入队引用对象(如 phantomreference)的通知队列,不存储原对象,仅用于感知回收事件;必须与 reference 子类构造时绑定,通过 poll()/remove() 读取,无公开写入方法。

ReferenceQueue 是用来收“尸体通知”的,不是存对象的
很多人一看到 ReferenceQueue 就以为它像 Queue 那样能存东西,结果往里 offer() 或 poll() 对象,发现啥也没——因为它根本不存被引用的对象本身,只收 JVM 在 GC 后“顺手塞进来”的引用对象(比如 PhantomReference 实例)。它的存在意义只有一个:让你知道“某个对象刚被回收了”。
典型误用:queue.add(obj) 或 new ReferenceQueue().put(ref) —— 这些操作根本不存在,ReferenceQueue 没有公开的写入方法,所有入队都由 JVM 自动完成。
- 必须配合
Reference子类(SoftReference、WeakReference、PhantomReference)构造时传入,例如:new PhantomReference(obj, queue) -
queue.poll()和queue.remove()是唯一读取方式;前者非阻塞,后者会阻塞直到有引用入队 - 如果没在构造引用时传入队列,JVM 回收后就真“静默”了,你完全感知不到
WeakReference + ReferenceQueue 是最常用的“对象存活监听”组合
想监控一个对象是否已被 GC,又不想阻止它被回收,WeakReference 加 ReferenceQueue 是最轻量、最稳妥的选择。注意:它只在 GC 发生且对象被判定为可清除后才入队,不是“对象变成弱可达时立刻通知”。
常见错误现象:queue.poll() 一直返回 null,哪怕你已经把原对象置为 null 并调用了 System.gc() —— 因为 GC 不保证立即发生,更不保证弱引用一定在这次 GC 中被清空。
立即学习“Java免费学习笔记(深入)”;
- 别依赖
System.gc()触发回收;它只是建议,JVM 可以忽略 - 测试时建议在循环中加
Thread.sleep(10)和多次System.gc(),但生产环境绝不能这么干 -
WeakReference.get()返回null只说明“可能已被回收”,真正确认要靠queue.poll() != null - 入队的是
WeakReference本身,不是它曾指向的对象;所以你要提前用ref.clear()避免意外复活(虽然弱引用本身不会阻止回收)
PhantomReference + ReferenceQueue 才能安全做清理,WeakReference 不行
如果你需要在对象被回收后执行资源释放(比如关闭文件句柄、释放 native 内存),必须用 PhantomReference,而不是 WeakReference。因为 PhantomReference.get() 永远返回 null,从源头杜绝了“对象已不可达却被意外访问”的风险。
容易踩的坑:PhantomReference 构造时第二个参数(ReferenceQueue)是 mandatory 的,漏传编译不过;而且它不能和 finalize() 混用——JVM 明确规定,一旦对象关联了 PhantomReference,finalize() 就不会被调用。
- 清理逻辑必须放在单独线程里轮询
queue.remove(),不能在主线程等,否则卡住整个流程 - 拿到
PhantomReference后,它已经无法访问原对象,所以所有需要的数据(如文件描述符、buffer 地址)必须在引用创建前保存到引用子类里 - JDK 9+ 中
Cleaner是更推荐的替代方案,但底层仍是基于PhantomReference+ReferenceQueue
ReferenceQueue 的线程安全性与性能影响
ReferenceQueue 本身是线程安全的,poll() 和 remove() 都是原子操作,但要注意:多个线程往同一个队列投递不同引用,没问题;但多个线程同时对同一个 Reference 实例调用 clear() 或反复 get(),就可能引发竞态——尤其是 WeakReference 在多线程下 get() 返回非 null 后又被其他线程触发 GC,导致后续访问 NPE。
性能上,入队动作由 JVM 在 GC 线程中完成,开销极小;但如果你在应用线程里频繁调用 queue.remove(0)(带超时的阻塞版本),而队列长期为空,会白白消耗 CPU 轮询。
- 不要在高频路径(如 request handler)里直接轮询
ReferenceQueue;适合用单后台线程持续remove() - 避免在
Reference子类里放大对象或闭包,它们会延长引用对象自身的生命周期 - Android 上低内存设备中,
WeakReference可能在任意 GC 时被清空,比桌面 JVM 更激进,测试务必覆盖内存压力场景








