Python - 垃圾回收(GC)机制
CPython 垃圾回收(GC)机制:底层原理、执行流程与面试要点适用范围:CPython 3.x。本文重点解释 CPython 的对象生命周期、引用计数(Reference Counting)、循环垃圾回收(Cyclic GC)、分代/代际管理以及内存分配器之间的关系。具体实现会随 Python 版本演进,尤其是 Python 3.14 对 cyclic GC 的代际模型进行了调整,因此文中会明确区分稳定的原理与版本相关的实现细节。1. GC 的整体模型Python 中通常所说的“垃圾回收”并不是单一算法,而是一组协同工作的机制。对 CPython 而言,最核心的是引用计数:对象的最后一个强引用消失后,通常可以立即进入销毁流程;引用计数无法处理的不可达循环引用,再由 cyclic GC 检测和回收。对象被销毁以后,释放出来的内存又交给 CPython 的内存分配器进行复用或在适当情况下归还底层分配系统。因此需要严格区分三个层次:对象是否已经死亡由引用计数和 cyclic GC 判断;对象如何执行销毁由对象类型的释放逻辑决定;释放后的内存如何管理由 allocator、pymalloc、free list 等机制负责。Python 引用关系 ↓ 对象生命周期判断 ↓ ┌───────────────┬────────────────┐ │ refcount → 0 │ 仍存在引用/环 │ │ 立即进入销毁 │ │ └───────┬───────┴───────┬────────┘ │ │ │ Cyclic GC 检测 │ ↓ │ 不可达循环 │ ↓ └────────→ 对象销毁 ↓ CPython allocator ↓ 复用 / 归还2. 引用计数:CPython 的第一层回收机制CPython 对对象采用引用计数来跟踪其生命周期。一个对象可以被多个变量、容器元素、函数参数以及解释器内部结构引用;对象内部维护的引用计数表示当前仍存在多少个有效引用。需要注意,引用计数不是“变量名数量”,而是对象引用关系的运行时状态。例如:a=[]b=a c=b可以抽象为:a ──┐ b ──┼──→ List Object c ──┘ refcount ≈ 3执行del b只会删除b → object这一条引用,通常使引用计数减少;它并不意味着对象本身一定被删除。只有当最后一个有效强引用消失,使引用计数降为 0 时,CPython 才能确定对象已经不存在可达引用,并进入对象销毁流程。引用计数的基本路径可以抽象为:产生引用 ↓ INCREF / 增加引用计数 ↓ 对象继续存活 ↓ 解除引用 ↓ DECREF / 减少引用计数 ↓ refcount == 0 ? ├─ 否 → 对象继续存活 └─ 是 → 对象释放逻辑 → 内存交给 allocator引用计数最大的优势是确定性和局部性:当最后一个引用消失时,不需要扫描整个堆即可判断该对象可以销毁。因此 CPython 中大量普通对象并不需要等待一次显式的gc.collect()才会死亡。3. 为什么引用计数无法独立解决垃圾回收引用计数存在一个根本缺陷:它无法仅凭refcount 0判断对象是否仍然能从程序外部到达。最典型的情况是循环引用:a=[]b=[]a.append(b)b.append(a)deladelb对象关系可以表示为:┌───────┐ │ ↓ 外部 X A ───→ B ↑ │ └─────┘a和b这两个外部变量已经删除,但 A 和 B 仍然互相引用,因此它们的引用计数不会降到 0。此时对象已经从程序其余部分不可达,却因为环内部的引用继续保持非零计数。于是引用计数无法发现它们是垃圾,这正是 cyclic GC 存在的原因。关键区别是:循环引用本身不等于垃圾。如果一个循环仍然可以从程序中的某个外部可达对象访问,它就是正常的活对象;只有整个循环与外部可达对象集合断开后,才属于不可达垃圾。4. Cyclic GC:处理不可达循环Cyclic GC 不是替代引用计数,而是引用计数的补充。它主要针对能够形成引用关系的容器对象,例如 list、dict、set、自定义实例以及某些 tuple 等。简单的整数、字符串等不