引用计数法无法解决循环引用问题。因对象间相互引用导致计数器无法归零,即使外部无引用,它们仍被判定为存活,造成内存泄漏;现代jvm改用可达性分析算法,从gc roots出发遍历标记可达对象,不可达者无论计数多少均被回收。

循环引用让引用计数“卡在1”
引用计数法靠每个对象内部的计数器判断是否可回收:有引用就+1,断开就-1,归零才释放。但当两个对象互相持有对方的引用时,哪怕外部已没有任何变量指向它们,它们的计数器也始终无法归零——因为彼此还在“算作一个引用”。
Java中典型的双向引用示例
下面这段代码创建了两个对象 objA 和 objB,并让它们相互赋值:
public class ReferenceCountingGC {
public Object instance = null;
private byte[] bigSize = new byte[2 * 1024 * 1024]; // 占用约2MB内存,便于观察
public static void main(String[] args) {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
执行后:
• 初始时,objA 和 objB 各被局部变量引用一次 → 计数=1
• objA.instance = objB → objB 被多引用一次 → 计数变为2
• objB.instance = objA → objA 被多引用一次 → 计数变为2
• 紧接着 objA = null 和 objB = null → 各自失去局部变量引用,计数减为1
• 此时两者计数均为1(因仍被对方引用),不满足“计数=0”条件 → 不会被回收
更隐蔽的三对象环状引用
循环引用不限于两个对象。比如 A→B、B→C、C→A 构成三角环:
- A 的
next指向 B - B 的
next指向 C - C 的
next指向 A
即使把所有外部变量(如 a、b、c)全部置为 null,A、B、C 的引用计数仍各为1 —— 它们像锁链一样彼此“挽留”,谁也无法退出。这种结构在链表节点、树节点、监听器注册等场景中容易无意形成。
为什么现代JVM不用它?
不是因为实现难,而是它无法区分“真存活”和“假存活”:
- 无法识别无外部可达路径的环状对象组
- 每次赋值都要更新两个对象的计数器,带来写屏障(Write Barrier)开销
- 不支持软/弱/虚引用语义,无法配合JVM的精细化内存管理策略
所以JVM统一采用可达性分析:从 GC Roots 出发做图遍历,只要不在引用链上,哪怕计数非零,也照收不误。










