大家好,我是正在实战各种 AI 项目的程序员晚枫。
引用计数有个致命缺陷:无法处理循环引用。CPython 如何解决这个难题?
理解垃圾回收机制,能帮你避免内存泄漏,也能解释为什么有些对象的 __del__ 不会被调用。
想象一下,你有一堆书,每本书上贴着一个标签,记录有多少人正在借阅这本书。当标签上的数字变成 0 时,你就可以把书扔掉。这就是引用计数。但如果有两本书互相引用(A 书说 B 书在借阅,B 书说 A 书在借阅),即使没有人真正需要它们,标签数字也不会变成 0。这就是循环引用问题。
🔄 引用计数的局限性
循环引用问题
让我们看一个经典的循环引用示例:
1 | # 简单的循环引用示例 |
这个例子展示了引用计数的致命缺陷:当两个或多个对象互相引用时,即使它们已经不再被程序使用,引用计数也不会归零,导致内存无法释放。
更隐蔽的例子
循环引用在实际代码中可能更隐蔽:
1 | class Node: |
在这个例子中,parent 和 children 形成了双向引用。如果程序没有正确清理这些引用,就可能导致内存泄漏。
🎯 分代垃圾回收器
CPython 使用分代垃圾回收(Generational GC)来解决循环引用问题。
核心思想
分代垃圾回收基于一个观察:对象存活时间越长,越可能长期存活。
基于这个观察,CPython 把对象分为三代:
1 | 0 代(年轻代):新创建的对象 |
回收策略:
- 0 代:最频繁检查,因为大多数对象很快就死亡
- 1 代:中等频率检查
- 2 代:最少检查,因为老年代对象大多是长期存活的
三代对象的数据结构
1 | // Modules/gcmodule.c |
| 代数 | 特点 | 检查频率 |
|---|---|---|
| 0 代 | 新创建的对象 | 最频繁 |
| 1 代 | 经过一次 0 代回收仍存活 | 中等 |
| 2 代 | 经过多次回收仍存活 | 最少 |
触发条件
1 | import gc |
为什么是容器对象?因为只有容器对象(列表、字典、集合等)才可能参与循环引用。简单的整数、字符串不可能形成循环引用,所以不需要跟踪。
🔍 循环检测算法
可达性分析
GC 通过可达性分析来识别垃圾:
1 | 1. 从根对象开始(全局变量、栈上变量等) |
这个过程被称为”标记 - 清扫”(Mark and Sweep)算法。
具体实现
1 | static Py_ssize_t |
回收过程
一次完整的 GC 回收包括以下步骤:
1 | static Py_ssize_t |
这个过程比较复杂,但核心思想是:暂时减去内部引用,如果某个对象的引用计数变成 0,说明它是循环引用的一部分,应该被回收。
💡 实战:观察垃圾回收
强制触发 GC
1 | import gc |
检测循环引用
1 | import gc |
使用 objgraph 可视化
1 | pip install objgraph |
1 | import objgraph |
objgraph 可以生成引用关系图,帮助你可视化内存中的对象引用。
⚠️ GC 的陷阱与最佳实践
1. del 方法的问题
1 | class BadExample: |
有 __del__ 方法的对象,GC 不能保证调用它。因为如果两个互相引用的对象都有 __del__ 方法,GC 无法确定先调用哪个。
推荐使用上下文管理器:
1 | class GoodExample: |
2. 禁用 GC 的风险
1 | import gc |
某些性能敏感的场景(如游戏帧渲染)会临时禁用 GC,但要小心内存膨胀。
3. 弱引用打破循环
1 | import weakref |
使用 weakref 可以避免循环引用。弱引用不增加引用计数,所以不会阻止对象被回收。
🎯 本讲总结
通过本讲,我们深入理解了:
引用计数的局限:无法处理循环引用,需要 GC 补充。
分代垃圾回收:三代对象的分级管理,提高回收效率。
可达性分析:如何识别循环引用的垃圾。
GC 触发时机:阈值机制与手动触发。
最佳实践:weakref、上下文管理器等避免循环引用的技巧。
📚 推荐教材
《Python 编程从入门到实践(第 3 版)》 - Eric Matthes 著
Python 零基础入门首选。本书分为基础语法和项目实战两部分,适合完全没有编程经验的读者。
《流畅的 Python(第 2 版)》 - Luciano Ramalho 著
Python 进阶经典之作。深入讲解 Python 的高级特性,包括数据模型、函数式编程、面向对象、元编程等。
《CPython 设计与实现》 - Anthony Shaw 著
本书深入讲解 CPython 内部机制,从内存管理到字节码执行,从对象模型到并发编程。配合本课程学习,效果更佳。
学习路线建议:
1 | 零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》 |
🔗 课程导航
← 上一讲:内存管理机制 | 下一讲:词法分析器 Tokenizer →
💬 联系我
| 平台 | 账号/链接 |
|---|---|
| 微信 | 扫码加好友 |
| 微博 | @程序员晚枫 |
| 知乎 | @程序员晚枫 |
| 抖音 | @程序员晚枫 |
| 小红书 | @程序员晚枫 |
| B 站 | Python 自动化办公社区 |
主营业务:AI 编程培训、企业内训、技术咨询