菜单

垃圾回收

相关源文件

本文档描述了 CPython 的垃圾回收系统,该系统负责在对象不再使用时回收内存。CPython 采用混合内存管理方法,结合了引用计数和循环垃圾回收器。引用计数为大多数对象提供确定性清理,而循环垃圾回收器则处理引用计数本身无法解决的引用循环。

有关内存分配的通用信息,请参阅内存管理

概述

CPython 的垃圾回收系统包含两个主要组件:

  1. 引用计数:当对象的引用计数达到零时,将其取消分配的主要内存管理机制。
  2. 循环垃圾回收器:一个辅助系统,用于检测和收集引用循环。

来源

引用计数

引用计数系统是 CPython 内存管理的基础。每个对象都有一个引用计数,当创建对象的新引用时,该计数会增加,当引用被移除时,该计数会减少。

基本机制

每个 Python 对象在创建时引用计数为 1。引用计数通过以下操作进行维护:

  • Py_INCREF(obj):增加对象的引用计数。
  • Py_DECREF(obj):减少对象的引用计数,并可能取消分配对象。
  • Py_XINCREF(obj)Py_XDECREF(obj):安全版本,会检查 NULL 指针。

当对象的引用计数达到零时,会调用其取消分配函数(由对象类型在 tp_dealloc 中指定),并释放其内存。

来源

引用循环问题

仅靠引用计数无法处理引用循环。考虑这个例子:

在这种情况下,即使 ab 不再能从 Python 代码中访问,它们仍然相互引用,因此它们的引用计数永远不会达到零。这就是循环垃圾回收器发挥作用的地方。

来源

循环垃圾回收

循环垃圾回收器 (GC) 旨在识别和收集组成引用循环但无法从程序中访问的对象。

分代设计

CPython 的垃圾回收器是分代的,它根据对象在收集周期中的存活情况将对象组织到不同的代中。

在标准的 CPython 实现中:

  • 年轻代 (0):新对象放置在此处。
  • 老年代 (1 & 2):存活下来的对象会被提升到更老的世代。
  • 回收器在较老世代的运行频率较低。

来源

跟踪对象

垃圾回收器仅跟踪可能构成引用循环的对象。这通常包括:

  • 可以包含对其他对象引用的容器对象(列表、字典、类、实例)。
  • 具有 __del__ 方法的对象。

不能参与引用循环的对象(如整数、字符串)为提高效率而不被跟踪。

跟踪对象的过程在标准实现和无锁线程实现之间有所不同。

标准实现无锁线程实现
使用 PyGC_Head 结构。使用 ob_gc_bits 字段。
基于链表的跟踪。基于位标志的跟踪。
_PyObject_GC_TRACK/UNTRACK_PyObject_SET_GC_BITS

来源

回收算法

CPython 中的回收过程遵循以下主要步骤:

  1. 更新引用计数:将每个对象的引用计数复制到临时 gc_refs 字段。
  2. 减去内部引用:对于每个跟踪的对象,递减它所引用的对象的 gc_refs
  3. 查找根节点:引用计数 gc_refs 非零的对象是可从跟踪对象外部访问的。
  4. 标记可达对象:从根节点开始,标记所有可达的对象。
  5. 移动不可达对象:将未标记为可达的对象移至“不可达”列表。
  6. 处理终结器:处理具有终结器(__del__ 方法)的对象。
  7. 处理弱引用:清除指向不可达对象的弱引用。
  8. 取消分配循环:释放循环中对象的内存。
  9. 提升幸存者:将幸存的对象移动到下一代。

来源

处理终结器

具有终结器(__del__ 方法)的对象需要特殊处理。如果具有终结器的对象是引用循环的一部分,则没有明显的调用终结器的顺序。

在 CPython 中,当引用循环包含具有终结器的对象时:

  1. 整个循环变得“不可回收”(以防止对象复活问题)。
  2. 如果设置了 DEBUG_SAVEALL,则此类对象将被移至 gc.garbage
  3. 否则,将打印关于不可回收对象的警告。

来源

无锁线程垃圾回收

随着 Python 3.13 中引入无 GIL 构建选项,实现了一个专门的无锁线程执行垃圾回收器。该回收器与标准实现有几个关键区别:

主要区别包括:

  1. 引用计数:使用原子操作和线程本地计数器。
  2. 对象跟踪:使用 ob_gc_bits 中的位标志,而不是单独的结构。
  3. 循环检测:使用“停止世界”方法,所有线程在回收期间暂停。
  4. 延迟引用计数:为了获得更好的性能,一些引用仅在垃圾回收期间进行计数。
  5. 标记存活阶段:明确标记从根节点可达的对象。

来源

延迟引用计数

无锁线程构建在某些引用类型上使用延迟引用计数以提高性能。来自堆栈和其他一些源的引用不会立即计数,而是在垃圾回收期间进行处理。

这种方法显著减少了临时引用的引用计数开销,这对于原子操作成本更高的无锁线程环境尤其重要。

来源

GC 的 Python 接口

CPython 通过 gc 模块公开 GC 功能,同时提供用户控制和诊断功能。

基本 GC 控制

阈值决定了每个世代自动收集的频率。

来源

GC 调试

gc 模块提供了调试功能,以帮助诊断内存泄漏。

DEBUG_SAVEALL 设置时,不可收集的对象将被存储在 gc.garbage 中,而不是无引用地留在内存中。

来源

GC 信息和统计

该模块还提供了用于获取已跟踪对象信息和收集统计的功能

来源

实现细节

GC 跟踪机制

在标准实现中,已跟踪对象在内存中的实际对象之前有一个 PyGC_Head 结构

PyGC_Head 包含用于维护每个代对象的双向链表的指针。

在自由线程实现中,跟踪是通过 PyObject 本身的 ob_gc_bits 字段进行的

_PyGC_BITS_TRACKED    (1<<0)  // Tracked by the GC
_PyGC_BITS_FINALIZED  (1<<1)  // tp_finalize was called
_PyGC_BITS_UNREACHABLE (1<<2) // Object determined unreachable
_PyGC_BITS_FROZEN     (1<<3)  // Object should not be collected
_PyGC_BITS_SHARED     (1<<4)  // Object memory is shared between threads
_PyGC_BITS_ALIVE      (1<<5)  // Reachable from a known root
_PyGC_BITS_DEFERRED   (1<<6)  // Use deferred reference counting

来源

GC 阈值和触发

当对象分配超过基于对象分配的阈值时,GC 会自动运行

  1. 每次分配已跟踪对象时,计数器会递增
  2. 当计数器超过第 0 代的阈值时,收集开始
  3. 在对第 0 代进行一定次数的收集后,将收集第 1 代
  4. 同样,在对第 1 代进行一定次数的收集后,将收集第 2 代

可以使用 gc.collect() 手动触发收集,这对于测试或需要立即回收内存时很有用。

来源

特殊注意事项

不朽对象

某些对象被标记为“不朽”,不受普通引用计数的约束

不朽对象具有特殊的引用计数,可防止其被解除分配。

来源

优化

采用了多项优化措施,以提高 GC 系统的效率

  1. 取消跟踪:无法形成循环的对象(如仅包含不可变对象的元组)将被取消跟踪
  2. 空闲列表:频繁分配/释放的对象保留在空闲列表中以供重用
  3. 预取指令:在自由线程实现中,使用 CPU 缓存预取以提高性能

来源

结论

CPython 的垃圾回收系统结合了引用计数和循环垃圾回收器,以提供高效的内存管理。引用计数系统可以即时处理大部分内存回收,而循环垃圾回收器则负责处理引用计数无法处理的引用循环。

理解这两个组件对于编写内存效率高的 Python 代码至关重要,特别是对于那些可能形成引用循环的长期运行应用程序或处理复杂数据结构的应用程序。

Python 3.13 中引入的自由线程实现增加了复杂机制,使垃圾回收可以在没有全局解释器锁 (Global Interpreter Lock) 的情况下高效运行,从而提高了 Python 应用程序的并行性。

来源