ReferenceQueue 必须显式传入 WeakReference 构造函数才能绑定,否则无法接收回收通知;它可被多种引用共用但不跨 JVM;需主动轮询 poll() 或 remove() 发现失效引用;清理时需提前保存上下文信息,因入队的是引用对象而非原始对象。

ReferenceQueue 是怎么和 WeakReference 绑定的
ReferenceQueue 不是自动关联弱引用的,必须在创建 WeakReference 时显式传入,否则它永远收不到被回收对象的通知。
常见错误是只 new 一个 WeakReference,以为 GC 后它会自己“上报”,结果 queue.poll() 一直返回 null——因为压根没连上队列。
-
new WeakReference(obj):没绑定队列,queue形同虚设 -
new WeakReference(obj, queue):正确绑定,GC 后该引用会被入队 - 同一个
ReferenceQueue可被多个不同类型的引用(WeakReference、PhantomReference)共用,但不能跨 JVM 实例共享
为什么 cleanup 必须轮询 poll() 而不是等 GC 触发回调
Java 没有“对象被回收时自动调用某方法”的机制。ReferenceQueue 的作用就是提供一个可主动检查的通道,靠你定期 poll() 或 remove() 来发现哪些引用已失效。
不轮询,就等于把清理逻辑扔进黑洞——资源泄漏不会报错,只会慢慢吃光内存或句柄。
立即学习“Java免费学习笔记(深入)”;
-
queue.poll()非阻塞,适合在业务线程中轻量检查,返回null表示暂无待清理项 -
queue.remove()会阻塞,适合放在单独守护线程里,避免空转消耗 CPU - 别在 finalize() 里做清理:该方法已被弃用,且不保证执行时机和次数
WeakReference + ReferenceQueue 清理资源的典型漏点
最容易忽略的是:ReferenceQueue 里放的是引用对象本身(比如 WeakReference 实例),不是它曾经指向的原始对象。原始对象在入队前已被 GC 回收,拿不到业务数据了。
所以想清理数据库连接、文件句柄这类带上下文的资源,必须在引用创建前就把关键信息(如 ID、路径、超时时间)存到引用子类里,或者用 Map 做外部映射。
- 错误写法:
map.put(ref, resource)——ref入队后,resource还在 map 里,泄漏 - 正确做法:继承
WeakReference,把resourceID存为字段;或用ConcurrentHashMap配合, ResourceMeta> cleanUp()扫描 - 注意
ReferenceQueue本身不释放任何资源,只是个信号器;真正的 close / release 必须由你手动触发
ReferenceQueue 在高并发场景下的线程安全边界
ReferenceQueue 的 poll() 和 remove() 是线程安全的,但它的内容不是“实时可见”的——GC 线程把引用入队和你的业务线程调用 poll() 之间存在微小窗口,可能漏掉刚入队的项。
这不是 bug,是设计使然。如果你依赖“绝对不漏”,就得接受额外成本:比如用 remove(0) 加重试,或配合 AtomicBoolean 标记清理状态。
- 单线程轮询足够应对大多数缓存场景(如本地 HTTP 连接池)
- 多线程竞争时,
remove()内部用的是锁,吞吐量不如无锁结构,别把它当高性能队列用 - 不要在
ReferenceQueue上加 synchronized 块——它内部已同步,再套一层反而降低并发度
ReferenceQueue 本身不复杂,难的是把“对象不可达”这个瞬态信号,稳稳地转化成你可控的清理动作。真正容易崩的,从来不是队列,而是你忘了原始资源在哪、怎么关、有没有被其他强引用拖着没死透。









