虚引用无法获取对象实例,仅用于在对象被回收后接收通知;必须用while循环轮询ReferenceQueue,因其入队时机异步不可预测,需通过独立线程持续调用remove()或poll()监听。

虚引用(PhantomReference)是 Java 四种引用类型中最弱的一种,它**无法通过它获取对象实例**,唯一作用就是:在对象被垃圾回收器回收后,收到一个“通知”。而这个通知机制,正是靠 ReferenceQueue 配合 while 循环来实现的。
为什么必须用 while 循环轮询 ReferenceQueue?
虚引用被加入队列的时机是:对象已通过 finalize(如果存在且未被禁用)并被真正回收后,JVM 将其对应的 PhantomReference 实例放入关联的 ReferenceQueue。但这个过程是异步的、不可预测的——你不能知道它何时发生,也不能阻塞等待(除非用带超时的 remove(long))。所以最常用、最稳妥的方式就是启动一个单独线程,用 while 持续调用 queue.poll() 或 queue.remove() 来监听。
典型监控结构:创建 + 注册 + 监听
三步缺一不可:
-
创建虚引用并绑定队列:构造
PhantomReference时,必须传入非 null 的ReferenceQueue,否则它永远无法入队; - 强引用保持对象存活:只要还有强引用指向原对象,虚引用就不会被入队(哪怕已无软/弱引用);
- 独立线程 + while 循环监听队列:避免阻塞主线程,也避免漏掉入队事件。
代码示例:监控一个资源对象的回收
假设你要监控某个大缓冲区(BigBuffer)是否被回收,以便及时释放本地内存:
ReferenceQueue<bigbuffer> refQueue = new ReferenceQueue();
BigBuffer buffer = new BigBuffer(1024 * 1024);
PhantomReference<bigbuffer> phantomRef = new PhantomReference(buffer, refQueue);
<p>// 启动监控线程
Thread monitor = new Thread(() -> {
System.out.println("开始监听虚引用回收...");
while (!Thread.currentThread().isInterrupted()) {
try {
// remove() 会阻塞直到有引用入队;poll() 立即返回 null 或引用
PhantomReference<bigbuffer> ref = (PhantomReference<bigbuffer>) refQueue.remove();
System.out.println("检测到 BigBuffer 被回收,执行清理逻辑");
// ⚠️ 注意:此处 ref.get() 总是 null,不要尝试取对象!
cleanupNativeResources(); // 例如释放 malloc 分配的内存
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
monitor.setDaemon(true); // 设为守护线程,避免阻止 JVM 退出
monitor.start();</bigbuffer></bigbuffer></p>
<p>// 后续某处让 buffer 失去强引用
buffer = null;
System.gc(); // 仅建议,不保证立即触发</p></bigbuffer></bigbuffer>关键注意事项
- 虚引用 get() 永远返回 null:这是设计使然,你只能拿到引用本身,不能反查对象——对象此时已不可达;
- 必须手动清理,JVM 不帮你:虚引用不参与任何自动资源管理,所有清理逻辑(如关闭文件、释放 native 内存)都得你自己写;
- 避免在队列处理中做耗时操作:否则会卡住整个队列监听线程,导致后续虚引用堆积;可把清理任务提交到线程池;
-
注意循环退出条件:使用
isInterrupted()判断,配合interrupt()安全关闭监听线程。










