PhantomReference 是唯一能精准锚定对象堆内存回收前最后一刻的引用类型,配合 ReferenceQueue 可安全及时清理堆外资源;它不阻止回收,入队时机严格对应“不可达+finalize完成+即将回收”,比 finalize 和 WeakReference 更可靠可控。

PhantomReference 本身不能阻止对象被回收,但它能让你在对象被 GC 回收、但尚未真正从堆中清除(即 finalize 阶段已过、内存还未释放)的“临界时刻”收到通知。配合 ReferenceQueue,它是实现堆外内存(如 DirectByteBuffer 底层的 native memory)安全、及时清理的推荐方式——比 finalize() 可靠,比 Cleaner(Java 9+)更底层可控。
为什么不用 finalize 或 WeakReference?
finalize 方法不可靠:不保证何时执行、可能不执行、有性能开销,且已被标记为 deprecated;WeakReference 在 GC 后立即入队,但此时对象可能还被其他强引用持有,无法确保“即将被彻底回收”;而 PhantomReference 的入队时机严格对应“对象已不可达 + 所有 finalize 已完成 + 即将回收其堆内存”,是唯一能准确锚定“堆内对象生命终结前最后一刻”的引用类型。
核心步骤:创建 PhantomReference + 关联清理逻辑
关键不在 PhantomReference 本身,而在它入队后你主动触发的清理动作。你需要一个独立的清理线程(或复用 ForkJoinPool.commonPool() 中的守护线程)持续轮询 ReferenceQueue:
- 构造 PhantomReference 时,传入一个自定义的清理资源对象(如 CleanerTask),或直接在入队后通过反射/字段访问获取关联的堆外地址(如 DirectByteBuffer 的 address 字段)
- ReferenceQueue.poll() 或 remove() 获取已入队的 PhantomReference,调用 get() 总是返回 null(这是 PhantomReference 的设计特性),所以必须提前把清理所需信息(如 long address、int capacity)保存在外部映射或作为清理任务的一部分
- 执行 native 调用(如 Unsafe.freeMemory(address))或关闭文件描述符等操作,确保堆外资源释放
典型实践:模拟 DirectByteBuffer 的清理逻辑
以封装 native 内存为例:
class OffHeapBuffer {
private final long address;
private final int size;
private final PhantomReference<offheapbuffer> ref;
public OffHeapBuffer(int size) {
this.size = size;
this.address = Unsafe.getUnsafe().allocateMemory(size);
// 将自身引用与清理任务绑定
this.ref = new PhantomReference(this, REF_QUEUE);
// 保存清理所需数据(不能放 ref 里,因为 ref 没有 referent 可用)
CLEANUP_MAP.put(ref, new CleanupTask(address));
}
// 静态队列和清理线程(只启动一次)
private static final ReferenceQueue<offheapbuffer> REF_QUEUE = new ReferenceQueue();
private static final Map<phantomreference>, CleanupTask> CLEANUP_MAP = new ConcurrentHashMap();
static {
Thread cleanupThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
PhantomReference> ref = (PhantomReference>) REF_QUEUE.remove(100);
if (ref != null) {
CleanupTask task = CLEANUP_MAP.remove(ref);
if (task != null) task.run();
}
} catch (InterruptedException e) {
break;
}
}
}, "off-heap-cleaner");
cleanupThread.setDaemon(true);
cleanupThread.start();
}
}</phantomreference></offheapbuffer></offheapbuffer>
注意事项与避坑点
PhantomReference 不会自动入队,必须由 GC 显式触发;入队不等于已回收,只是“可回收状态已确认”,此时仍可安全访问堆外资源;避免在清理逻辑中重新创建强引用到该对象(会导致内存泄漏);清理任务应尽量轻量、无阻塞、不抛异常(否则可能卡住整个清理线程);JDK 自身的 DirectByteBuffer 正是用类似机制(结合 Cleaner)实现堆外内存管理,可参考其 sun.misc.Cleaner 实现源码理解底层协作逻辑。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











