引言
Python (CPython) 的内存管理策略与 Java 和 Go 有显著不同。它主要依赖引用计数 (Reference Counting) 来进行内存回收,辅以一个分代循环垃圾收集器 (Generational Cyclic GC) 来解决引用循环问题。
1. 引用计数 (Reference Counting)
这是 Python 内存管理的基石。每个 Python 对象都有一个
ob_refcnt 字段。
1.1 机制详解
- 增加引用:
当对象被赋值给变量、作为参数传递、加入容器时,
ob_refcnt加 1。 - 减少引用: 当变量离开作用域、被显式
del、从容器移除时,ob_refcnt减 1。 - 回收: 当
ob_refcnt降为 0 时,立即调用对象的析构函数 (__del__) 并释放内存。
伪代码 (C 语言风格):
#define Py_INCREF(op) (op->ob_refcnt++)
#define Py_DECREF(op) \
if (--(op->ob_refcnt) == 0) \
_Py_Dealloc(op)1.2 优缺点
- 优点:
- 实时性: 内存一旦不再使用立即回收,没有 STW 延迟。
- 简单: 实现逻辑直观。
- 缺点:
- 开销大: 每次变量赋值都要更新计数,原子操作(如果多线程)昂贵(Python 有 GIL,所以不需要原子操作,但仍有 CPU 开销)。
- 无法处理循环引用: 这是致命伤。
2. 循环引用与分代收集
为了解决 A -> B -> A
这种循环引用导致内存无法释放的问题,Python 引入了分代循环
GC。
2.1 容器对象 (Container Objects)
只有可能包含引用的对象(如 list, dict, class, tuple)才会被 GC 跟踪。简单的原子类型(如 int, str)不需要 GC 跟踪,因为它们不可能构成环。
2.2 循环检测算法
Python 使用一种基于引用计数副本的算法来检测环。
算法步骤 (伪代码):
def collect(generation):
# 1. 建立引用计数副本
# gc_refs = ob_refcnt
update_gc_refs()
# 2. 减去内部引用
# 遍历该代中所有对象,将其引用的对象的 gc_refs 减 1
# 这一步是为了消除"环"内部的引用影响
subtract_refs()
# 3. 标记可达对象
# 此时,如果 gc_refs > 0,说明有外部引用指向它,它是活跃的。
# 将其标记为 reachable,并递归标记其引用的对象。
move_unreachable_to_garbage()
# 4. 回收垃圾
# 剩下的 gc_refs == 0 的对象就是不可达的环
delete_garbage()2.3 分代假设 (Generational Hypothesis)
Python 将对象分为三代:0 代、1 代、2 代。 * 0 代: 新创建的对象。 * 1 代: 幸存过一次 0 代回收的对象。 * 2 代: 幸存过一次 1 代回收的对象。
触发机制: * 当 0 代对象数量减去回收数量达到阈值(默认 700),触发 0 代 GC。 * 当 0 代 GC 发生一定次数(默认 10 次),触发 1 代 GC。 * 以此类推。
3. 性能调优
Python 提供了 gc 模块来控制垃圾回收。
gc.disable(): 关闭自动 GC(引用计数仍在工作)。在对性能要求极高且确信无循环引用的场景下使用。gc.collect(): 手动触发 Full GC。gc.set_threshold(): 调整分代阈值。
总结
Python 的 GC 是 引用计数 (主) + 分代循环 GC (辅)。 * 引用计数保证了大多数对象的实时回收。 * 分代 GC 兜底解决循环引用,避免内存泄漏。