强引用“砍不断”因其不参与GC决策,只要变量未置null或未出作用域,JVM宁抛OutOfMemoryError也不回收;软引用仅在OOM前可能被回收,适合缓存;弱引用GC时立即回收,适用于WeakHashMap;虚引用仅用于回收通知,需配合ReferenceQueue。

强引用为什么“砍不断”?——它不参与GC决策
强引用就是你每天写的 Object obj = new Object(),只要变量 obj 还在栈帧里活着(没被设为 null、没出作用域),哪怕堆内存快爆了,JVM 也宁可抛 OutOfMemoryError,绝不会动它。这不是“不想收”,而是 GC 根可达算法直接把它标为“必须存活”。
常见错误现象:
- 把大对象(如 byte[])放进静态集合后忘了清理 → 内存泄漏
- 在监听器注册后没解绑,而监听器又被外部类强引用持有着 → 整个 Activity 或 Fragment 无法回收
- 显式断开是唯一可靠方式:
obj = null,且确保没有其他强引用路径(比如被static Map、线程局部变量、内部类隐式持有) - 别依赖
System.gc():它只是建议,不保证触发,更不保证回收强引用对象
软引用适合做缓存,但别指望它“稳住”
SoftReference 的回收时机由 JVM 自行判断,规则是:**只有在即将 OOM 前的某次 GC 中才可能被回收**。它不是“内存够就永远留着”,而是“尽量留着,实在扛不住才扔”。
典型场景:图片缓存、解析后的 XML/JSON 数据缓存。
- 不要用
softRef.get()后不判空就直接用 —— 它随时可能返回null,尤其在低内存设备或频繁 GC 场景下 - 避免和强引用共存:如果同时有强引用指向同一对象,软引用就完全失效(因为对象始终强可达)
- 想感知回收事件?得配
ReferenceQueue:new SoftReference(obj, queue),然后轮询queue.poll()
弱引用“一扫就倒”,WeakHashMap 是它的主战场
WeakReference 的行为很干脆:**只要 GC 线程路过,不管内存多富裕,只要对象只被弱引用连着,立刻回收**。它比软引用更“佛系”,也更危险。
立即学习“Java免费学习笔记(深入)”;
最稳妥的用途是 WeakHashMap —— 它的 key 是弱引用,当 key 对象本身没其他强引用时,整个键值对自动消失,避免内存泄漏。
- 别用弱引用保存需要稳定访问的业务对象(比如用户 session),它可能在你调用前一秒就被清掉了
- ThreadLocal 的底层实现就靠弱引用 key,防止线程长期运行导致 value 泄漏;但如果你忘了调用
remove(),value 仍会滞留(因为 value 是强引用) - 测试时别信
System.gc()立刻生效 —— GC 线程优先级低,加Thread.sleep(100)再检查更靠谱
虚引用不是用来“拿对象”的,而是用来“收尸通知”的
PhantomReference 是唯一一个 .get() 永远返回 null 的引用。它不阻止回收,也不提供对象访问,存在的唯一意义是:**当对象被 finalize 之后、内存真正释放之前,JVM 会把它塞进关联的 ReferenceQueue**。
所以它只干一件事:资源清理钩子,比如关闭文件句柄、释放 off-heap 内存(ByteBuffer.allocateDirect())、记录对象销毁日志。
- 必须搭配
ReferenceQueue使用,否则等于白写:new PhantomReference(obj, queue) - 不能在
finalize()里复活对象(已废弃),也不能在虚引用中保留强引用到原对象,否则队列永远等不到它 - 实际开发中极少手写 —— Netty、NIO、某些高性能缓存库底层用得多,业务代码一般通过封装好的生命周期回调间接使用
真正容易被忽略的点是:四种引用不是互斥开关,而是组合工具。比如 Guava Cache 默认用软引用做 entry 缓存,但 key 用的是弱引用;又比如一个对象既被软引用又同时被弱引用持有时,弱引用那条路径会在第一次 GC 就失效,而软引用还可能苟几轮 —— 引用强度不叠加,只看最强那条链是否断裂。










