Python 垃圾回收
CPython 引用计数 + 分代循环垃圾回收;理解循环引用与 gc 模块边界。
#concept
#growing
#dev
#python
#python
[!info] related notes
Python 垃圾回收
这个概念为什么出现
两个对象互相引用时,引用计数无法归零。CPython 引入循环检测 GC。不懂会在排查“内存不降”时误判。
[!abstract] 一句话理解 CPython 用引用计数配合循环 GC;gc 模块用于观察和调试。回收时机与代际策略属于具体版本和构建的实现,不能替代显式资源管理。
最小可观察示例
场景:构造循环引用并手动收集
# cycle_gc_demo.py
# 业务意图:演示循环引用与 gc.collect。
# 教学点:循环需要 GC;实现细节。
import gc
class Node:
def __init__(self, name: str) -> None:
self.name = name
self.ref = None
def main() -> None:
gc.collect()
a, b = Node("a"), Node("b")
a.ref, b.ref = b, a # 循环
del a, b
n = gc.collect()
print("collected + uncollectable objects:", n)
if __name__ == "__main__":
main()
建议运行:
python cycle_gc_demo.py
期望输出:collected + uncollectable objects: 后为非负整数。这是已收集对象与不可收集对象数量之和,不是循环数量;可能包含其他垃圾,不应断言恰好为 2。
结合场景再看三个关注点
- del 名字不等于立刻解开循环。
- gc.collect() 统计的是对象;一次循环可能涉及多个实例与容器。
- 循环本身不等于泄漏;弱引用适用于不应拥有对象的关系,不应机械替换所有强引用。
核心概念与准确模型
- 引用计数
- 代际与阈值行为需查目标补丁版本文档;例如 CPython 3.14 系列中代际策略已有调整。
- 可调阈值(高级)
__del__与周期回收的复杂交互
边界情况与反直觉行为
__del__复活对象- gc.disable() 关闭自动循环收集,不会关闭引用计数,也不阻止显式 gc.collect()。
- gc 不是通用原生内存管理器;支持 GC 协议的扩展对象可参与循环检测,外部库缓冲仍需其释放协议。
常见误区
[!warning] 常见误区:Python 没有 GC 只有计数 错误理解:单机制。
正确模型:计数 + 循环 GC(CPython)。
工程实践
- 缓存用弱引用/显式上限
- 排查泄漏:对象增长曲线
- 不要在业务里频繁玩 gc 旋钮
本节总结
GC 是安全网。设计上仍应管理对象生命周期与缓存边界。
自测题
- 循环引用为何难倒纯引用计数?
__del__为何危险?
参考答案
- 互相持有导致计数永不为 0。
- 回收时机、顺序和对象复活使终结器难以推理;现代 CPython 通常能收集含 del 的循环,不能把有终结器直接等同于不可收集。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| gc | 文档 | 模块 |
| Supporting Cyclic GC | C-API | 实现向 |