引用计数法无法识别循环引用,根本原因在于它只统计引用数量而不判断可达性:a与b互相强引用时,即使外部无引用,二者计数仍为1,形成无法释放的“孤岛”;python通过gc模块的标记-清除机制从根对象出发检测并回收不可达循环,而weakref通过不增加引用计数的方式主动打破闭环。
引用计数法无法识别循环引用,根本原因在于它只认“数字”,不认“路径”——它只统计有多少个引用指向我,却完全不知道这些引用来自哪里、是否还连通到程序的活根上。
引用计数只做加减法,不判断可达性
每个对象内部存一个整数(ob_refcnt),规则极简单:
- 有新变量或容器指向它 → 计数 +1
- 变量被赋新值、离开作用域、从容器中删除 → 计数 −1
- 计数归零 → 立刻释放内存
它不追踪“这个引用是从全局变量来的?还是从栈帧来的?还是从另一个早已孤立的对象来的?”——只要计数没到零,就默认“还有人需要它”。而循环引用中,A 持有 B、B 持有 A,哪怕外部所有变量都消失了,A 和 B 的计数仍各为 1。它们彼此就是对方的“最后一个人”,系统无从察觉这两人早已被世界遗忘。
循环引用构成逻辑上的“孤岛”,但计数器看不见墙
想象内存是一张大地图,程序运行时真正活跃的部分(如主线程栈、全局模块变量)是几座“灯塔”(即 GC Roots)。正常对象通过引用链像道路一样连向灯塔;而循环引用形成的 A↔B 结构,就像一座没有桥通往任何灯塔的孤岛——岛上两人互相握手,但岛外没人能找得到他们。
引用计数机制根本没有“灯塔”和“道路”的概念,它只在每栋房子门口贴个计数牌。孤岛上的房子计数牌写着“1”,它就认为房子还在住人,绝不会去查“这人是不是靠隔壁那人活着”。
Python 的补救:gc 模块用标记-清除破岛
CPython 在引用计数之外,额外启用了一套基于可达性的垃圾收集器(gc模块),专门对付这类孤岛:
- 从真实根对象(如栈帧里的局部变量、模块级全局变量)出发,沿着所有引用链“点亮”可达对象
- 所有未被点亮的对象,无论计数多少,都被判定为不可达 → 加入待回收队列
- 它会主动扫描容器类型(list/dict/class 实例等)内部的引用关系,发现 A→B→A 这样的闭环,并整体清理
注意:这套机制不是实时触发的,而是按代(generation)积累一定数量容器对象后才运行;所以循环引用不会立刻释放,可能延迟几十毫秒到几秒,取决于分配节奏和 gc 阈值。
为什么弱引用能打破循环?
弱引用(weakref)本质是“不参与计数的引用”:它让一个对象能被访问,但不增加对方的 ob_refcnt。比如把 B 存成 A 的 weakref.ref(B),那么 A 持有 B 的弱引用,B 的计数不会因此 +1;当外部引用全部消失,B 的计数就能顺利归零,被立即回收,随后 A 失去目标,弱引用自动失效——环自然解开。
这不是修补计数法,而是绕开它:把原本强依赖的关系,降级为“可有可无”的观察关系。










