进阶篇 · Advanced
lv.3 进阶
kp-033
内存管理与垃圾回收
1. 一句话定义
CPython 以引用计数为主、分代垃圾回收为辅:引用归零立即释放;循环引用由 GC 周期性清扫——理解这两层才能诊断"内存为什么涨上去不下来"。
2. 为什么重要
长驻进程(Web 服务、爬虫)的内存问题 90% 不是"GC 不干活",而是该释放的对象仍被引用着(全局缓存、闭包、异常 traceback)。会排查内存,是服务端 Python 的硬技能。
3. 前置知识
4. 核心概念
- 引用计数:每个对象记录"被引用次数";
del x、名字重绑、容器移除都会减计数;归零立即回收。sys.getrefcount(x)查看(注意自身多 1 个临时引用)。 - 循环引用:
a.b = b; b.a = a计数永不归零 → 交给分代 GC。 - 分代 GC:0/1/2 三代,新对象在 0 代;扫描越老越少、越老越慢。
__slots__:用固定槽位替代实例__dict__,大批量小对象省内存 30-50%。- weakref:弱引用不增加计数——缓存场景的标准解。
5. 原理与机制
python
import sys, gc, weakref
a = [1, 2]
print(sys.getrefcount(a)) # 2(a 本身 + 传参临时引用)
b = a # +1
del a # -1;对象仍活着(b 还指它)
class Node: pass
x, y = Node(), Node()
x.peer, y.peer = y, x # 循环引用
del x, y # 计数未归零
gc.collect() # 分代 GC 清扫循环垃圾
# 常见滞留源
class Handler:
def __init__(self):
self.cache = {}
def handle(self, exc):
self.cache["last"] = exc # 异常对象带着 traceback → 引用整条调用栈帧!tracemalloc 定位增长点:
python
import tracemalloc
tracemalloc.start()
... # 运行一段时间
snap = tracemalloc.take_snapshot()
for stat in snap.statistics("lineno")[:5]:
print(stat) # 哪一行分配的内存最多6. 关键事实(模型/图示)
text
主通道:refcount → 0 → 立刻析构(确定性、快)
辅通道:分代 GC → 清循环引用(threshold 可调;gc.disable() 对纯脚本常安全)
第三通道:weakref → 不阻止回收(观察者/缓存的标准姿势)
大对象特例:NumPy 等缓冲区走 malloc 之外的通道,refcount 照常管理7. 直观类比
引用计数是会议室门上的签到表:进出各划一笔,最后一个人走(计数归零)随手关灯锁门——即时且确定。循环引用像两个房间互留钥匙,谁也别想"最后离开"——于是物业(分代 GC)定期巡楼,发现整层没人却亮着灯就统一拉闸。
8. 实例与案例
python
# 缓存标准写法:弱引用值
import weakref
cache = weakref.WeakValueDictionary()
# 大列表小对象:__slots__
class Point:
__slots__ = ("x", "y")
def __init__(self, x, y): self.x, self.y = x, y
# 1e6 个 Point:带 dict ≈ 152MB → slots ≈ 56MB(数量级演示)9. 常见误区
- "内存涨 = 泄漏 = GC 坏了" —— 多数是全局容器只进不出(缓存无上限、日志追加);先
tracemalloc定位。 - 捕获异常后长期保存异常对象 —— 异常的
__traceback__持有整条栈帧(局部变量全在);记录日志后丢弃或只存字符串。 - 认为
del x会立即释放 —— 只减引用;还有别的名字/容器/闭包引用着就活着。 __slots__万能 —— 它放弃了动态属性;且与多重继承的某些组合冲突,有明确内存痛点再上。
10. 自测题
- 循环引用为什么引用计数解决不了?谁来解决?
sys.getrefcount(a)返回值为什么总比"真实引用数"大 1?- 服务内存持续上涨的第一步排查动作?
参考答案
- 互相引用使计数永不归零;分代垃圾回收器通过周期性遍历检测不可达的循环结构。
- 函数传参本身创建了一个临时引用。
- 开 tracemalloc 对比两个时间点的快照(compare_to),定位增长最快的分配点。
11. 与其他知识点的关系
12. 延伸阅读
- gc 模块文档;tracemalloc 文档
- 《CPython Internals》内存管理章节