25 分钟
Python 高级

内存模型与垃圾回收

理解引用计数、分代回收、循环引用和内存泄漏排查

  • 理解引用计数机制
  • 理解分代垃圾回收(三代)
  • 理解循环引用问题
  • 学会用 tracemalloc 排查内存泄漏

内存模型与垃圾回收

Python 内存管理是自动化的,但懂原理能帮你避开内存泄漏,写出更高效的代码。

引用计数

每个 Python 对象都有一个引用计数器(ob_refcnt),记录有多少个引用指向它:

示例代码(可运行)

循环引用问题

两个对象互相引用,引用计数永远不会归零,这种情况引用计数处理不了。

示例代码(可运行)
⚠️__del__ 的坑

循环引用里的对象如果定义了 __del__,Python 3.4 之前的 GC 没法回收它们,会丢去 gc.garbage。Python 3.4 之后改了,但还是尽量别定义 __del__,用上下文管理器或者 close() 方法代替。

分代垃圾回收

Python 用三代分代回收,靠的是这么个假设:活得越久的对象,越不可能是垃圾。

示例代码(可运行)

内存泄漏排查

示例代码(可运行)
🐍资深工程师经验谈

内存泄漏排查三板斧:tracemalloc 对比快照定位分配位置objgraph.show_growth() 看哪类对象在增长gc.garbage 查看无法回收的对象。常见泄漏原因:全局缓存无限增长、闭包持有大对象、循环引用 + __del__、信号连接未断开、C 扩展泄漏。生产环境定期监控内存趋势,突增就是信号。

弱引用:打破循环引用

示例代码(可运行)
算法原理

GC 如何检测循环引用

分代 GC 只跟踪容器对象(list/dict/class instance 等可能产生循环引用的对象),不跟踪 int/str 等简单类型:

GC 检测循环引用的过程(简化)

从所有容器对象出发,复制引用计数从「根对象」(全局引用、栈上引用)出发,标记可达对象 每到达一个对象,将其复制的计数 -1复制计数 > 0 的对象,说明只被循环引用,是垃圾回收这些垃圾对象这就是「标记-清除」算法的变种只在容器对象链表上运行,不扫描所有内存所以比全堆扫描快得多

选择题

Python 引用计数无法处理什么问题?

选择题

tracemalloc 的主要用途是?

资深工程师加餐

底层原理 · 大厂视角 · 工程经验,点卡片展开

@decorator 写在函数定义上方,它在「模块加载、函数被定义时」就执行一次,把原函数包装成新函数;之后每次调用,走的都是包装后的版本。带参数的装饰器本质是三层嵌套(外层收参数、中层收函数、内层收调用参数)。务必用 functools.wraps 保留原函数名和文档,否则调试栈和日志里全是 wrapper。

挑战任务

循环引用检测器

简单+50 XP

创建两个互相引用的对象,用 gc 模块检测并统计回收情况。

循环引用检测器
1 个测试用例

课后作业

内存追踪器

中等+25 XP

用 tracemalloc 追踪列表推导式的内存分配,输出增长最多的代码位置。

内存追踪器
1 个测试用例