Python垃圾回收以引用计数为主,辅以gc模块的代际循环检测和弱引用;gc不处理原子类型、未正确实现C扩展对象及隐式长持引用对象;含__del__的循环引用会滞留于gc.garbage。

Python 的垃圾回收主要靠引用计数,但不止于此
Python 默认启用引用计数(sys.getrefcount() 可查),对象每被一个变量、容器或栈帧引用,计数就 +1;引用失效时 -1。一旦计数归零,对象立即被释放——这是最及时、最主导的回收方式。
但引用计数无法处理循环引用:比如 A 持有 B,B 又持有 A,即使外部再无引用,两者的计数都不为 0。这时就得靠另外两个机制兜底:
-
gc模块的周期性循环检测(基于生成器标记-清除) - 弱引用(
weakref)绕过计数,避免强引用闭环
什么时候触发 gc.collect()?默认阈值怎么调
Python 启动时会自动启用 gc,并设置三档代际阈值(gc.get_threshold() 默认返回 (700, 10, 10)):第 0 代每新增 700 个对象就检查一次;第 1 代在第 0 代触发 10 次后检查;第 2 代同理。
这些阈值不是固定时间间隔,而是按对象分配/回收次数驱动的。你可以用 gc.set_threshold(300, 5, 5) 调得更激进,但代价是 CPU 开销上升;设为 (0, 0, 0) 则完全禁用自动循环检测(不推荐,除非你 100% 确保没循环引用)。
立即学习“Python免费学习笔记(深入)”;
手动调用 gc.collect(0) 只清理第 0 代,比全代 gc.collect() 更轻量,适合在关键路径后快速释放短期循环引用。
哪些对象不会被 gc 检测到?常见漏网之鱼
gc 模块只管理“可收集对象”:即定义了 __del__ 方法、或属于容器类型(如 list、dict、class 实例)的对象。而以下几类根本不在它的扫描范围内:
- 原子类型:如
int、str、tuple(不可变且无__del__)——全靠引用计数搞定 - C 扩展创建的对象(如 NumPy 数组),若未正确实现
tp_traverse和tp_clear,可能逃逸检测 - 被全局变量、模块级字典、线程局部存储(
threading.local())长期持有的对象,即使逻辑上已废弃,仍算“活跃引用”
这也是为什么有时 gc.collect() 返回 0 却内存没降——问题不在 GC 失效,而在对象仍被某个隐式引用链挂着。
__del__ 和循环引用的危险组合
如果一个参与循环的对象定义了 __del__,gc 不会直接回收它,而是把它移到 gc.garbage 列表中等待人工干预。因为 Python 无法安全确定 __del__ 的执行顺序,怕引发未定义行为。
这意味着:一旦出现带 __del__ 的循环引用,那些对象就卡在内存里,直到你手动清空 gc.garbage 或重启解释器。
更隐蔽的是,__del__ 中若抛出异常,Python 只会打印警告(ResourceWarning),不会中断流程,容易掩盖真正的问题源头。
所以,除非真需要资源清理钩子,否则优先用上下文管理器(with)或显式 .close(),避开 __del__。










