深入解析Python垃圾回收机制:引用计数、标记清除与分代回收
本文详细解析Python的垃圾回收(GC)机制,涵盖以引用计数为主、分代回收为辅的核心原理。重点阐述引用计数的运作方式及其无法解决循环引用的缺陷,介绍标记清除算法如何处理容器对象的内存回收,并解析分代回收如何通过年轻代、中年代和老年代的划分优化GC效率。
Python垃圾回收机制概述
一、引用计数
引用计数原理与优缺点
Python 的垃圾回收机制主要依赖于引用计数,并辅以分代回收策略。引用计数法的核心原理是为每个对象维护一个名为 ob_refcnt 的字段,用于记录当前指向该对象的引用数量。当出现以下四种情况之一时,对象的引用计数会加 1:
- 对象被创建,例如执行赋值语句 a = 14;
- 对象被其他变量引用,例如执行 b = a;
- 对象作为参数传递给函数,例如调用 func(a);
- 对象被放入容器结构中,例如 List = {a, 'a', 'b', 2}。
与上述增加引用的情况相对应,当发生以下四种情况时,对象的引用计数会减 1:
对象的别名被显式销毁,例如执行 del a; 对象的别名被重新赋值为新的对象,例如执行 a = 26; 对象离开其作用域,例如函数 func() 执行完毕时,函数内部所有局部变量的引用计数都会减 1; 从容器中删除元素,或者容器本身被销毁。 当对象的引用计数降为 0 时,Python 虚拟机会立即回收该对象占用的内存。
在 Python 中,一切皆对象,其核心数据结构为 Py_Object。所有 Python 对象的头部都包含这一结构:
// object.h
#define PyObject_HEAD
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
typedef struct _object {
PyObject_HEAD
} PyObject;
以 int 类型为例,其定义如下:
// intobj.h
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
简而言之,PyObject 是每个对象必须具备的基础内容,其中的 ob_refcnt 字段负责记录引用计数。每当对象获得新的引用时,ob_refcnt 增加;当对象的引用被删除时,ob_refcnt 减少。一旦引用计数变为 0,该对象的生命周期即告结束。
// object.h
#define Py_INCREF(op) (
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA
((PyObject*)(op))->ob_refcnt++)
#define Py_DECREF(op)
do {
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA
--((PyObject*)(op))->ob_refcnt != 0)
_Py_CHECK_REFCNT(op)
else
_Py_Dealloc((PyObject *)(op));
} while (0)
引用计数机制具有明显的优势:
- 高效性;
- 运行期无停顿,具备实时性:对象一旦失去所有引用,会立即被释放。实时性还带来一个额外好处,即回收内存的时间分摊到了平时的引用操作中;
- 对象拥有确定的生命周期;
- 实现简单易懂。
然而,原始的引用计数法也存在明显的缺点:
- 维护引用计数需要消耗系统资源,维护成本与引用赋值的次数成正比;
- 无法解决循环引用问题。
例如:
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
标记清除算法解析
为了解决上述两个致命弱点,Python 引入了另外两种 GC 机制。
二、标记-清除
『标记-清除(Mark-Sweep)』算法是一种基于追踪回收(tracing GC)技术的垃圾回收算法。它包含两个阶段:第一阶段是标记阶段,GC 会给所有『活动对象』打上标记;第二阶段是清除阶段,回收那些没有被标记的『非活动对象』。那么,GC 如何判断哪些是活动对象、哪些是非活动对象呢?
对象之间通过引用(指针)连接,构成一个有向图。对象是有向图的顶点,引用关系是有向图的弧。从根对象(root object)出发,遍历有向图,将可达(reachable)的对象标记为活动对象,不可达的对象则被视为非活动对象。根对象通常包括全局变量、调用栈和寄存器。

在上图中,将小黑圈视为全局变量,即作为 root object。从小黑圈出发,对象 1 可直接到达,因此被标记;对象 2、3 可间接到达,也会被标记;而对象 4 和 5 不可达,因此 1、2、3 是活动对象,4 和 5 是非活动对象,会被 GC 回收。
标记清除算法作为 Python 的辅助垃圾回收技术,主要用于处理容器对象,例如 list、dict、tuple、instance 等,因为字符串、数值等原子类型的对象不可能造成循环引用问题。Python 使用双向链表将容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:在清除非活动对象前,必须顺序扫描整个堆内存,哪怕只剩下小部分非活动对象,也要扫描所有对象。
分代回收策略
三、分代回收
分代回收是一种以空间换时间的操作方式。Python 根据对象的存活时间将内存划分为不同的集合,每个集合称为一个代。Python 将内存分为 3 代,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),它们对应 3 个链表。垃圾回收频率随着对象存活时间的增大而减小。新创建的对象都会被分配到年轻代,当年轻代链表的节点总数达到上限时,Python 垃圾收集机制就会被触发,把可以被回收的对象回收掉,而不能被回收的对象会被移到中年代去。依此类推,老年代中的对象是存活时间最久的对象,甚至存活于整个系统的生命周期内。分代回收建立在标记清除的基础之上,同样作为 Python 处理容器对象的辅助垃圾回收技术。
以上就是 python 中的 GC 机制的详细内容,更多关于 python GC 的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









