1453 字
7 分钟
深入浅出Python的内存回收机制
2026-03-03

一、核心:引用计数器(RefCnt)

引用计数器是Python内存管理的“第一道防线”,也是最基础、最常用的回收逻辑。

每个Python对象(如int、list、dict)的底层结构体中,都内置了 ob_refcnt (引用计数字段),用来记录当前有多少个“引用”在使用这个对象。当对象被创建/赋值给变量时, ob_refcnt +1 ;当变量被删除(del)、超出作用域、重新赋值时, ob_refcnt -1 ;一旦 ob_refcnt == 0 ,说明这个对象无任何引用,Python会立刻回收该对象占用的内存(归还给解释器,而非直接还给操作系统)。

底层支撑:Python在C源码中通过两个核心结构体实现这一逻辑:

PyObject :所有Python对象的基类,包含 ob_refcnt (引用计数)、 ob_type (对象类型)等公共字段;

PyVarObject :继承自 PyObject ,新增 ob_size 字段,专门用于记录列表、元组等“变长容器”的元素个数。

运行时结构:Python启动后会创建一个名为 refchain 的双向链表,所有容器对象(list、dict、set等)都会被加入这个链表,链表节点中直接关联对象的 ob_refcnt ,方便解释器快速遍历和更新引用计数。

但是引用计数器的致命问题:循环引用

单纯依赖引用计数器会出现“漏回收”——当两个(或多个)对象互相引用时,即使外部无任何引用,它们的 ob_refcnt 也永远不为0。 示例:

v1 = []
v2 = []
v1.append(v2) # v1引用v2,v2的ob_refcnt +1
v2.append(v1) # v2引用v1,v1的ob_refcnt +1
del v1 # v1的ob_refcnt -1(仍为1,因为v2还引用它)
del v2 # v2的ob_refcnt -1(仍为1,因为v1还引用它)

此时v1、v2无外部引用,但互相引用,引用计数器失效,内存无法回收

二、辅助1:标记清除(Mark-Sweep)

标记清除是专门解决“循环引用”的补充机制,只针对可能产生循环引用的“容器对象”(list、dict、set等,数值型/字符串等不可变对象不会产生循环引用)。

  • 分“标记”和“清除”两步执行:
  1. 标记阶段:解释器维护一个专门存放“容器对象”的链表,当触发回收时,先遍历该链表,将所有对象的 ob_refcnt 复制一份作为“临时计数”;然后递归遍历对象的引用关系,若发现A引用B,则将B的临时计数-1;
  2. 清除阶段:遍历链表,若某个对象的临时计数仍>0,说明有外部引用,保留;若临时计数=0,说明仅存在循环引用,标记为“可回收”,随后清除这些对象并释放内存。
  • 触发时机:标记清除不会频繁执行,而是结合“分代回收”触发,避免过度消耗CPU。

三、辅助2:分代回收(Generational GC)

分代回收的核心思想是:存活越久的对象,越不可能被回收,因此可以减少扫描频率,以此提升垃圾回收的整体效率。 Python将对象划分为3个“代”(0/1/2代),对应3个独立的链表:

  1. 代的划分规则:
  • 新创建的对象先进入0代链表;
  • 0代链表触发扫描后,未被回收的对象会升级到1代;
  • 1代链表触发扫描后,未被回收的对象会升级到2代。
  1. 触发阈值(默认):
  • 0代:当链表中对象数量达到700个时,触发一次标记清除扫描;
  • 1代:当0代累计扫描10次后,触发1代的标记清除扫描;
  • 2代:当1代累计扫描10次后,触发2代的标记清除扫描。
  1. 核心优势:大部分新创建的对象(如临时变量)生命周期短,集中在0代快速扫描;而长期存活的对象(如全局变量)极少扫描,既保证了回收效率,又降低了CPU开销。

四、性能优化:缓存机制(FreeList/小对象池)

为了避免“频繁创建-销毁小对象”带来的性能损耗,Python引入了缓存机制——回收的对象不会立刻销毁,而是放入 free_list (自由列表)缓存,新创建对象时优先从缓存中复用,而非向操作系统申请新内存。

  • 小整数池:Python默认缓存 [-5, 256] 范围内的整数,这些整数会被全局复用,创建时无需重新分配内存;
  • 字符串驻留池:对于纯字母/数字/下划线组成的字符串(标识符),Python会缓存并复用,减少重复创建;
  • 容器free_list:list、dict、tuple等容器对象被回收后,会进入各自的 free_list (如list的free_list默认缓存80个空列表),新创建空容器时直接复用,无需初始化。
  • 关键特点: free_list 中的对象属于“解释器内缓存”,不会被标记清除/分代回收,只有当解释器退出或内存不足时,才会逐步释放给操作系统。

总结

Python内存管理的核心逻辑可概括为:

  1. 基础回收:通过 refchain 双向链表管理所有对象,依赖 ob_refcnt 引用计数器完成大部分内存回收;
  2. 问题解决:针对循环引用,通过标记清除机制扫描容器对象链表,识别并回收无外部引用的循环对象;
  3. 效率提升:分代回收按对象存活时长分级扫描,减少不必要的CPU消耗;
  4. 性能优化:通过free_list/小对象池缓存常用对象,避免频繁内存分配/销毁,兼顾易用性与性能。
深入浅出Python的内存回收机制
https://luoca.cn/posts/深入浅出python的内存回收机制/
作者
喵呜
发布于
2026-03-03
许可协议
CC BY-NC-SA 4.0