Python垃圾回收器不处理含__del__的循环引用,因其无法安全确定析构顺序,故将其移入gc.garbage永久封存;此类对象需手动破环或删除__del__方可释放。

Python 的垃圾回收器不是“无法释放”循环引用对象,而是根本不会去碰那些带 __del__ 方法的循环引用——它们被直接塞进 gc.garbage 永久封存。
为什么 __del__ 会让 GC 彻底放弃一个循环引用
这不是 bug,是 CPython 的主动规避策略:一旦对象定义了 __del__,且又陷入循环引用,GC 就无法安全决定谁的析构函数该先执行。它干脆不尝试,而是把整个循环体移入 gc.garbage 列表,后续所有 gc.collect() 调用都会跳过它们。
-
gc.collect(0)、gc.collect(1)、gc.collect(2)全部无效——只要对象在gc.garbage里,就永远不会被扫描 - 查
len(gc.garbage)> 0 是最直接的信号:你代码里肯定有类定义了__del__,又和别的容器对象互相持有强引用 - 即使你手动
del所有外部变量,只要这个循环还卡在gc.garbage,内存就永远不归还
gc.collect() 返回值为 0 不代表没回收,可能只是没扫到第 2 代
默认 gc.collect() 只清理第 0 代。而循环引用对象往往跨多轮存活,早被升到了第 2 代——它压根不在本次扫描范围内。
- 用
gc.get_threshold()查返回值类似(700, 10, 10):第 2 代触发需第 1 代回收满 10 次,实际业务中可能几小时都不触发一次 - 小规模循环(比如每秒只创建一对对象)几乎永远达不到第 2 代阈值,长期滞留内存
- 要真正清掉,必须显式调
gc.collect(2),而不是gc.collect()
弱引用破环失败的常见原因
很多人试过 weakref.ref 或 weakref.proxy,结果发现还是泄漏——问题往往出在组合使用上。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
立即学习“Python免费学习笔记(深入)”;
-
weakref.ref本身不参与引用计数,但若目标对象定义了__del__,弱引用会失效:CPython 禁止这种组合,运行时静默降级为普通强引用 - 用
weakref.WeakKeyDictionary缓存时,如果 key 是自定义类实例且含__del__,整个字典可能拒绝插入或行为异常 - 第三方库(如某些 ORM 或日志 handler)内部偷偷持有了强引用,
weakref根本没机会生效
真正泄漏高发场景:class 实例 + 容器嵌套 + __del__
纯 int、str、不含可变元素的 tuple 不进 GC;只有容器类型(list、dict、set、类实例)才参与循环检测。而泄漏最顽固的,就是同时满足三个条件的对象:
- 是类实例(即容器对象)
- 内部字段包含
list/dict并相互引用(如树节点的parent和children) - 类里写了
__del__(哪怕只为了打个 log)
这时候别指望 GC 自动救场——要么删掉 __del__ 改用上下文管理器,要么在销毁前手动切断引用链。否则,gc.garbage 就是你内存泄漏的日志本,翻都懒得翻。

















