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:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 构造 PhantomReference 时,传入一个自定义的清理资源对象(如 CleanerTask),或直接在入队后通过反射/字段访问获取关联的堆外地址(如 DirectByteBuffer 的 address 字段)
- ReferenceQueue.poll() 或 remove() 获取已入队的 PhantomReference,调用 get() 总是返回 null(这是 PhantomReference 的设计特性),所以必须提前把清理所需信息(如 long address、int capacity)保存在外部映射或作为清理任务的一部分
- 执行 native 调用(如 Unsafe.freeMemory(address))或关闭文件描述符等操作,确保堆外资源释放
典型实践:模拟 DirectByteBuffer 的清理逻辑
以封装 native 内存为例:
立即学习“Java免费学习笔记(深入)”;
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 不会自动入队,必须由 GC 显式触发;入队不等于已回收,只是“可回收状态已确认”,此时仍可安全访问堆外资源;避免在清理逻辑中重新创建强引用到该对象(会导致内存泄漏);清理任务应尽量轻量、无阻塞、不抛异常(否则可能卡住整个清理线程);JDK 自身的 DirectByteBuffer 正是用类似机制(结合 Cleaner)实现堆外内存管理,可参考其 sun.misc.Cleaner 实现源码理解底层协作逻辑。

















