weakreference适合内存敏感缓存,因其在gc时立即回收仅被其引用的对象,实现自动缩容;但get()可能突变为null,须配合referencequeue监听,且不可用于键值关联逻辑。

WeakReference 为什么适合做内存敏感缓存
因为 GC 时,只要对象只被 WeakReference 持有,就会被立即回收——不等下一次 Full GC,也不看堆内存是否充足。这正是缓存“自动缩容”的核心机制。
常见错误现象:WeakReference.get() 突然返回 null,但没报错、没日志,缓存命中率骤降却查不到原因。
- 必须配合
ReferenceQueue主动轮询或监听失效事件,不能只靠.get()判断 - 不要把
WeakReference当成“带自动清理的 Map.Entry”,它本身不参与键值关联逻辑 - 如果缓存 key 是弱引用,value 却是强引用,value 会因 key 被回收而永久泄漏(key=null 但 value 还在)
SoftReference 在 JVM 中的实际回收时机
SoftReference 不是“内存不够才回收”,而是“JVM 认为该回收时才回收”——具体由 -XX:SoftRefLRUPolicyMSPerMB 控制,默认每 MB 堆空间保留软引用 1000ms。这意味着:刚分配的软引用可能撑过几次 Minor GC,但在一次老年代压力上升后突然集体失效。
使用场景:适合做非关键但希望多留一会儿的缓存,比如模板渲染结果、静态资源字节数组。
立即学习“Java免费学习笔记(深入)”;
- OpenJDK 8+ 默认策略已改为 LRU-like,但依然不保证顺序或时间精度
- 不要依赖
SoftReference实现“内存阈值触发清理”,它无法响应MemoryMXBean或Runtime.freeMemory() - 和
WeakReference混用时,注意两者共存于同一ReferenceQueue时需用instanceof区分类型
PhantomReference 是唯一能安全执行清理逻辑的虚引用
PhantomReference 的 .get() 永远返回 null,它存在的唯一意义就是:对象被 GC 前,会被加入关联的 ReferenceQueue,此时你可以做释放堆外内存、关闭文件描述符等操作。
容易踩的坑:PhantomReference 必须搭配 ReferenceQueue 使用,且必须手动调用 queue.remove() 或轮询,否则清理逻辑永远不会执行;而且它不阻止对象被回收,所以不能用来“延缓 GC”。
- 别在
finalize()里做等价事——finalize()已被标记为 deprecated,且执行不可控、易阻塞 GC 线程 - 如果清理逻辑耗时(如写磁盘),务必异步处理,否则会拖慢 Reference Handler 线程,间接卡住整个 GC 流程
- 对象进入
PhantomReference队列时,其所有字段已不可访问(可能已被清零),只能依赖构造时传入的元数据
强引用缓存泄漏的典型链路与检测方式
你以为清掉了 Map,但其实 ThreadLocal、静态监听器、未注销的 java.beans.PropertyChangeListener、甚至 CompletableFuture 的默认线程池回调,都在悄悄持有着缓存 value 的强引用。
性能影响:这类泄漏不会立刻 OOM,但会让 GC 频繁扫描大量本该回收的对象,Minor GC 时间缓慢上升,最终触发更多 Full GC。
- 用
jcmd <pid> VM.native_memory summary</pid>查 native 内存增长趋势,比堆 dump 更早暴露问题 - 用
jmap -histo:live <pid></pid>对比两次结果,重点关注缓存类实例数是否只增不减 - 在缓存包装类中重写
toString(),打印 key 的哈希与引用类型,方便在 MAT 中按字符串筛选可疑对象
真正难的不是选哪种引用,而是确认你的缓存对象是否被其他地方悄悄强持有——GC 日志里看不到这种关系,得靠引用链分析工具一层层往上翻。










