Java垃圾回收采用可达性分析算法,以GC Roots为起点沿强引用链遍历,不可达对象被标记为可回收,天然解决循环引用问题。

OpenJDK(以及所有主流 JVM)中,两个对象互相引用(A ↔ B)却仍能被回收,根本原因不是“GC 特别聪明”,而是它压根不看对象之间怎么互相指——它只看:**从 GC Roots 能不能走到 A 或 B**。
为什么互相引用的对象不会卡住不回收
Java 从不使用引用计数法,所以不存在「A 引用 B、B 引用 A → 计数都不为 0 → 永远不回收」的问题。JVM 使用的是可达性分析(Reachability Analysis),判定逻辑非常直接:
- 如果 A 和 B 都没有任何外部变量、静态字段、栈帧局部变量、JNI 引用等指向它们 → 它们整体不可达 → 下次 GC 就会被回收
- 哪怕 A 和 B 内部有几十层嵌套引用链,只要入口断了,整片子图就“沉没”了
- 这个过程完全自动,无需你手动
null掉某个字段,也不依赖finalize()
什么情况下互相引用反而导致回收失败
互相引用本身无害,但若其中一环意外连回了 GC Roots,就会让整条链“活过来”。常见坑点包括:
- 静态缓存持有 A:
static Map<string a> cache = new HashMap(); cache.put("key", a);</string>→ A 可达 → B 也跟着可达 - 匿名内部类或 Lambda 捕获了外部对象,而该对象又持有了 A/B:比如事件监听器里闭包引用了 Activity,Activity 又持有 Fragment,Fragment 持有 ViewModel,ViewModel 持有回调接口 → 看似松散,实则强引用闭环
- ThreadLocal 中存了 A,而线程长期存活(如线程池)→ A 始终被
ThreadLocalMap的 Entry 强引用,Entry 的 key 是弱引用,但 value 是强引用 - 未清理的
WeakHashMapvalue:key 虽弱,但 value 是强引用;若 value 又反向引用了 key 或其他大对象,内存照样滞留
WeakReference 能打破循环,但不是万能解药
WeakReference 的作用是让某条引用不参与可达性判定,但它只对「持有它的那个引用变量」生效,不影响其他强引用:
- 写
WeakReference<b> ref = new WeakReference(b);</b>,只是让ref.get()不阻止 B 回收;如果 A 还强引用着 B,B 依然活得好好的 -
WeakHashMap的 key 是弱引用,但它的 value 是强引用 —— 若 value 是个大数组或缓存结果,且没有额外清理逻辑,value 就会拖住整个 entry 不释放 -
PhantomReference无法取回对象,get()恒为null;它只配合ReferenceQueue用作回收通知,不改变可达性
真正容易被忽略的,是那些看似“已经释放”的场景:局部变量出作用域、list.remove(obj)、甚至显式赋 obj = null,都不等于对象立刻不可达——只要还有任何一条隐式强引用链连回 GC Roots(比如未注销的监听器、未关闭的流、未清理的 ThreadLocal),它就还在内存里站着,不管它和谁互相引用。


















