Python引用计数与 GC 机制
Python 内存管理:引用计数与 GC 机制详解
摘要:Python 的内存管理是自动化的,但这不代表我们可以完全忽视它。本文深入拆解 Python 的内存管理基石——引用计数,以及它是如何通过“分代回收”与“标记-清除”算法来解决循环引用问题的。
一、 核心机制:引用计数 (Reference Counting)
Python 中一切皆对象,而每个对象的核心头部(PyObject)都包含了一个引用计数器 (ob_refcnt)。这是 Python 内存管理的主力军,解决了 90% 的内存回收问题。
1. 计数器运作规则
-
+1 (增加):
-
对象被创建:
a = [1, 2] -
被引用:
b = a -
作为参数传递:
func(a) -
作为容器元素:
list_c = [a] -
-1 (减少):
-
引用被删除:
del a -
引用指向新对象:
b = "hello" -
离开作用域:函数执行完毕,局部变量销毁。
-
从容器中移除:
list_c.remove(a)
2. 优点与缺点
-
优点:
-
实时性:一旦引用计数变为 0,内存立刻被回收,不需要等待。
-
逻辑简单:生命周期管理非常清晰。
-
缺点:
-
维护成本:频繁更新计数器需要消耗 CPU 资源。
-
致命伤:无法解决循环引用 (Circular Reference) 问题。
二、 致命伤:循环引用 (The "Island" Problem)
当两个对象互相引用,且没有外部变量指向它们时,就会形成一个“孤岛”。
a = []
b = []
a.append(b) # a 引用 b
b.append(a) # b 引用 a
del a
del b
执行 del 后,a 和 b 的引用计数都从 2 变成了 1(因为它们还在互相指着对方)。引用计数器认为它们还活着,所以不会回收。如果只有引用计数,这部分内存就永久泄露了。
三、 兜底机制:分代回收 (Generational GC)
为了解决循环引用,Python 引入了垃圾回收 (Garbage Collection) 机制。但如果时刻扫描所有对象,程序会卡死。因此,Python 采用了“分代回收”策略,基于一个统计学假设:越年轻的对象,越容易死;活得越久的对象,越可能是常驻内存。
1. 三代分层 (Generation 0, 1, 2)
Python 将所有对象分为三代:
- 第 0 代 (Young):新创建的对象都放在这里。GC 扫描最频繁。
- 第 1 代 (Middle):在第 0 代 GC 中幸存下来的对象,晋升到第 1 代。
- 第 2 代 (Old):在第 1 代 GC 中幸存下来的对象,晋升到第 2 代。扫描最少。
2. 标记-清除算法 (Mark and Sweep)
当 GC 触发时(通常是在第 0 代),Python 使用标记-清除算法处理循环引用:
- 寻找根对象 (Root Object):全局变量、当前栈帧中的引用等。
- 标记 (Mark):从根出发,沿着引用链遍历。能摸到的对象打上“可达”标记。
- 清除 (Sweep):遍历结束后,所有没被打上标记的对象(哪怕引用计数不为 0,如循环引用的孤岛),统统视为垃圾回收。
四、 实战验证:查看引用计数与 GC
我们可以使用 sys 和 gc 模块来验证上述理论。
1. 验证引用计数
import sys
a = []
# 结果通常是 2。
# 1 是因为变量 a 引用了它。
# +1 是因为 getrefcount 这个函数本身在执行时也短暂引用了它。
print(sys.getrefcount(a))
b = a
print(sys.getrefcount(a)) # 结果是 3
2. 触发垃圾回收
通常我们不需要手动调用,但在调试内存泄露或处理大数据量时,可以手动触发:
import gc
# 手动触发一次全量回收(扫描所有代)
gc.collect()
# 查看各代当前的计数(对象数量)
print(gc.get_count())
# 输出示例: (596, 12, 0) -> (0代对象数, 1代对象数, 2代对象数)
# 查看 GC 阈值
print(gc.get_threshold())
# 输出示例: (700, 10, 10)
# 解释:
# 当分配对象数 - 释放对象数 > 700 时,扫描第 0 代。
# 当第 0 代扫描 10 次后,扫描第 1 代。
五、 总结 (Interview Takeaways)
- 主力军:Python 主要依赖引用计数进行内存管理,一旦计数为 0,内存立即释放。
- 特种兵:为了解决引用计数无法处理的循环引用问题,Python 引入了基于标记-清除算法的 GC 机制。
- 优化策略:为了减少 GC 卡顿,Python 采用了分代回收(0/1/2 三代),对存活时间长的对象降低扫描频率,以空间换时间,提升效率。

浙公网安备 33010602011771号