Python的垃圾回收机制以引⽤计数器为主、分代码回收和标记清除为辅
1.refchai链表
在Python的C源码中有⼀个名为refchain的环状双向链表,在Python程序中每创建1个对象,就会将其加入此链表。
city = '四川' 内部会创建一个结构体,包含【上一个对象、下一个对象、类型、引用计数、值、其他特有属性】
num= 123

static PyObject refchain = {&refchain, &refchain}
1.1两个重要的结构体
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
// 宏定义,包含 上⼀个、下⼀个,⽤于构造双向链表⽤。(将对象放到refchain链表中时,需要⽤到)
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
typedef struct _object {
_PyObject_HEAD_EXTRA // ⽤于构造双向链表
Py_ssize_t ob_refcnt; // 引⽤计数器
struct _typeobject *ob_type; // 数据类型
} PyObject;
typedef struct {
PyObject ob_base; // PyObject对象
Py_ssize_t ob_size; /* Number of items in variable part,即:元素个数 */
} PyVarObjec;
这两个结构体PyObject和PyVarObject是基本结构,他们是各数据类型公共部分。PyVarObject主要在多元素对象创建时使用,它在PyObject基础上添加了一个ob_size用于记录元素个数。
例:单元素的对象在创建时大多使用PyObject,会包含其4部分基础数据;list/tuple/set等由多个元素组成对象创建时使用的PyVarObject,会包含其5部分基础数据
1.2常⻅类型结构体
- int类型
struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};
/* Long (arbitrary precision) integer object interface */
typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h *
- float类型
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObjec;
- str类型
typedef struct {
PyObject_HEAD
Py_ssize_t length; /* Number of code points in the string*/
Py_hash_t hash; /* Hash value; -1 if not set */
struct {
unsigned int interned:2;
/* Character size:
- PyUnicode_WCHAR_KIND (0):
* character type = wchar_t (16 or 32 bits, depending on the
platform)
- PyUnicode_1BYTE_KIND (1):
* character type = Py_UCS1 (8 bits, unsigned)
* all characters are in the range U+0000-U+00FF (latin1)
* if ascii is set, all characters are in the range U+0000-U+007F
(ASCII), otherwise at least one character is in the rangeU+0080-U+00FF
- PyUnicode_2BYTE_KIND (2):
* character type = Py_UCS2 (16 bits, unsigned)
* all characters are in the range U+0000-U+FFFF (BMP)
* at least one character is in the range U+0100-U+FFFF
- PyUnicode_4BYTE_KIND (4):
* character type = Py_UCS4 (32 bits, unsigned)
* all characters are in the range U+0000-U+10FFFF
* at least one character is in the range U+10000-U+10FFFF
*/
unsigned int kind:3;
unsigned int compact:1;
unsigned int ascii:1;
unsigned int ready:1;
unsigned int :24;
} state;
wchar_t *wstr; /* wchar_t representation (null-terminated) */
} PyASCIIObject;
typedef struct {
PyASCIIObject _base;
Py_ssize_t utf8_length; /* Number of bytes in utf8, excluding the
* terminating \0. */
char *utf8; /* UTF-8 representation (null-terminated) */
Py_ssize_t wstr_length; /* Number of code points in wstr, possible
* surrogates count as two code points.
*/
} PyCompactUnicodeObject;
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data; /* Canonical, smallest-form Unicode buffer */
} PyUnicodeObjec;
str类型⽐较繁琐,因为python字符串在处理时需要考虑到编码问题,在python内部规定(⻅源码结构体):
字符串只包含ascii,则每个字符⽤1个字节表示,即:latin1
字符串包含中⽂等,则每个字符⽤2个字节表示,即:ucs2
字符串包含emoji等,则每个字符⽤4个字节表示,即:ucs4


- list类型
typedef struct {
PyObject_VAR_HEAD
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObjec;
- tuple类型
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1];
} PyTupleObjec;
dict类型
typedef struct {
PyObject_HEAD
Py_ssize_t ma_used;
PyDictKeysObject *ma_keys;
PyObject **ma_values;
} PyDictObjec;
1.3 Float类型创建
val = 3.14
val = 3.14
内部会创建:
_ob_next = refchain中的上一个对象
_ob_prev = refchain中的下一个对象
ob_refcnt = 1 //引用计数
ob_type = float //类型
ob_fval = 3.14 //值
下面是创建到销毁的部分源码,有的可以暂时不关注
创建对象3.14
// Objects/floatobject.c
// ⽤于缓存float对象的链表
static PyFloatObject *free_list = NULL;
static int numfree = 0;
PyObject *
PyFloat_FromDouble(double fval)
{
// 如果free_list中有可⽤对象,则从free_list链表拿出来⼀个;否则为对象重新开辟内存。
PyFloatObject *op = free_list;
if (op != NULL) {
free_list = (PyFloatObject *) Py_TYPE(op);
numfree--;
} else {
// 根据float类型的⼤⼩,为float对象新开辟内存。
op = (PyFloatObject*) PyObject_MALLOC(sizeof(PyFloatObject));
if (!op)
return PyErr_NoMemory();
}
// 对float对象进⾏初始化,例如:引⽤计数器初始化为1、添加到refchain链表等。
/* Inline PyObject_New */
(void)PyObject_INIT(op, &PyFloat_Type);
// 对float对象赋值。即:op->ob_fval = 3.14
op->ob_fval = fval;
return (PyObject *) op;
}
加入refchain链表
// Include/objimpl.h
#define PyObject_INIT(op, typeobj) \( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op)
// Objects/object.c
// 维护了所有对象的⼀个环状双向链表
static PyObject refchain = {&refchain, &refchain};
void
_Py_AddToAllObjects(PyObject *op, int force)
{
if (force || op->_ob_prev == NULL) {
op->_ob_next = refchain._ob_next;
op->_ob_prev = &refchain;
refchain._ob_next->_ob_prev = op;
refchain._ob_next = op;
}
}
void
_Py_NewReference(PyObject *op)
{
_Py_INC_REFTOTAL;
// 引⽤计数器初始化为1。
op->ob_refcnt = 1;
// 对象添加到双向链表refchain中。
_Py_AddToAllObjects(op, 1);
_Py_INC_TPALLOCS(op);
}
添加引用计数
// Include/object.h
static inline void _Py_INCREF(PyObject *op)
{
_Py_INC_REFTOTAL;
// 对象的引⽤计数器 + 1
op->ob_refcnt++;
}
#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
销毁
val = 3.14
del va
在项⽬中如果出现这种删除的语句,则内部会将引⽤计数器-1,如果引⽤计数器减为0,则进⾏缓存或垃圾回收。
// Include/object.h
static inline void _Py_DECREF(const char *filename, int lineno, PyObject *op)
{
(void)filename; /* may be unused, shut up -Wunused-parameter */
(void)lineno; /* may be unused, shut up -Wunused-parameter */
_Py_DEC_REFTOTAL;
// 引⽤计数器-1,如果引⽤计数器为0,则执⾏ _Py_Dealloc去缓存或垃圾回收。
if (--op->ob_refcnt != 0) {
#ifdef Py_REF_DEBUG
if (op->ob_refcnt < 0) {
_Py_NegativeRefcount(filename, lineno, op);
}
#endif
}
else {
_Py_Dealloc(op);
}
}
#define Py_DECREF(op) _Py_DECREF(__FILE__, __LINE__, _PyObject_CAST(op)
// Objects/object.c
void
_Py_Dealloc(PyObject *op)
{
// 找到float类型的 tp_dealloc 函数
destructor dealloc = Py_TYPE(op)->tp_dealloc;
// 在refchain双向链表中摘除此对象。
_Py_ForgetReference(op);
// 执⾏float类型的 tp_dealloc 函数,去进⾏缓存或垃圾回收。
(*dealloc)(op);
}
void
_Py_ForgetReference(PyObject *op)
{
...
// 在refchain链表中移除此对象
op->_ob_next->_ob_prev = op->_ob_prev;
op->_ob_prev->_ob_next = op->_ob_next;
op->_ob_next = op->_ob_prev = NULL;
_Py_INC_TPFREES(op);
}
// Objects/floatobject.c
#define PyFloat_MAXFREELIST 100
static int numfree = 0;
static PyFloatObject *free_list = NULL;
// float类型中函数的对应关系
PyTypeObject PyFloat_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"float",
sizeof(PyFloatObject),
0,
// tp_dealloc表示执⾏float_dealloc⽅法
(destructor)float_dealloc, /* tp_dealloc */
0, /* tp_print */
...
};
static void
float_dealloc(PyFloatObject *op)
{
// 检测是否是float类型
if (PyFloat_CheckExact(op)) {
// 检测free_list中缓存的个数是否已满,如果已满,则直接将对象销毁。
if (numfree >= PyFloat_MAXFREELIST) {
// 销毁
PyObject_FREE(op);
return;
}
// 将对象加⼊到free_list链表中
numfree++;
Py_TYPE(op) = (struct _typeobject *)free_list;
free_list = op;
}
else
Py_TYPE(op)->tp_free((PyObject *)op);
}
2.引用计数器
val = 3.14
内部会创建:
_ob_next = refchain中的上一个对象
_ob_prev = refchain中的下一个对象
ob_refcnt = 1 //引用计数
ob_type = float //类型
ob_fval = 3.14 //值
如上,在python中,创建一个数据对象后,结构体中会有一个ob_refcnt参数,默认值是1
- 当有其他变量引用该对象时,该参数会+1
- 引用该对象的变量被删除时,该参数会-1
这就是对象的引用计数器
eg:
val = 3.14 //ob_refcnt = 1
vv = val //ob_refcnt = 2
del vv //ob_refcnt = 1
源码:
// Include/object.h
static inline void _Py_INCREF(PyObject *op)
{
_Py_INC_REFTOTAL;
// 对象的引⽤计数器 + 1
op->ob_refcnt++;
}
#define Py_INCREF(op) _Py_INCREF(_PyObject_CAST(op))
当引用计数器为0时,表示该对象没有被任何人使用,此时该对象就是垃圾,需要垃圾回收。
垃圾回收有两种情况:
- 对象从
refchain链移除后,将对象直接销毁,回收内存空间 - 对象从
refchain链移除后,将对象加入free_list(缓存链表中),等待之后再创建对象时直接提取(不需要重新开辟内存),初始化后使用,以提高效率
源码:参考1.3销毁部分
3.标记清除和分代回收
如果只使用引用计数器进行垃圾回收,在出现循环引用时,会导致对象无法回收,从而内存泄漏。
例如:
v1 = [11,22,33] # refchain中创建⼀个列表对象,由于v1=对象,所以列表引对象⽤计数器为1. ob_refcnt = 1
v2 = [44,55,66] # refchain中再创建⼀个列表对象,因v2=对象,所以列表对象引⽤计数器为1. ob_refcnt = 1
v1.append(v2) # 把v2追加到v1中,则v2对应的[44,55,66]对象的引⽤计数器加1,最终为2. ob_refcnt = 2
v2.append(v1) # 把v1追加到v1中,则v1对应的[11,22,33]对象的引⽤计数器加1,最终为2. ob_refcnt = 2
del v1 # 引⽤计数器-1 ob_refcnt = 1
del v2 # 引⽤计数器-1 ob_refcnt = 1
如上,现在外部已经没人在使用者两个对象了,但是由于存在循环引用,所以引用计数器仍然不为0。针对这种情况,python引入了标记清除机制。
标记清除:在python底层,创建特殊链表专⻔⽤于保存可能存在循环应⽤的类型(列表、元组、字典、集合、⾃定义类等对象),之后再去检查这个链表中的对象是否存在循环引⽤,如果存在则让双⽅的引⽤计数器均 - 1 。
在标记清除中,如果每次都全量扫描所有对象,那么效率会很低,为了优化这一问题,python引入了分代回收机制。
分代回收:对标记清除中的链表进⾏优化,将那些可能存在循引⽤的对象拆分到3个链表,链表分为:0/1/2三代,每代都可以存储对象,有自己特定的阈值,当达到阈值时,就会对相应的链表中的每个对象做⼀次扫描,将循环引⽤对象的计数各⾃减1,并且销毁引⽤计数器为0的对象。
gcmodule.c
struct gc_generation {
PyGC_Head head;
int threshold; /* collection threshold */ // 阈值
int count; /* count of allocations or collections of younger
generations */ // 实时个数
};
#define NUM_GENERATIONS 3
struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
{{(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0},
// 0代
{{(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0},
// 1代
{{(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0},
// 2代
};
特别注意:0代和1、2代的threshold和count表示的意义不同。
0代:count表示0代链表中对象的数量,threshold表示0代链表对象个数阈值,超过则执⾏⼀次0代扫描检查。
1代:count表示0代链表扫描的次数,threshold表示0代链表扫描的次数阈值,超过则执⾏⼀次1代扫描查。
2代:count表示1代链表扫描的次数,threshold表示1代链表扫描的次数阈值,超过则执⾏⼀2代扫描检查。
源码:
Modules/gcmodule.c
/* GC information is stored BEFORE the object structure. */
typedef union _gc_head {
struct {
// 建立链表需要的前后指针
union _gc_head *gc_next;
union _gc_head *gc_prev;
// 在初始化时会被初始化为 GC_UNTRACED
Py_ssize_t gc_refs;
} gc;
long double dummy; /* force worst-case alignment */
} PyGC_Head;
创建对象的过程: 对象 = pyGC_Head | PyObject_HEAD | Container Object
PyObject *
_PyObject_GC_New(PyTypeObject *tp)
{
PyObject *op = _PyObject_GC_Malloc(_PyObject_SIZE(tp));
if (op != NULL)
op = PyObject_INIT(op, tp);
return op;
}
=> _PyObject_GC_Malloc
#define _PyGC_REFS_UNTRACKED (-2)
#define GC_UNTRACKED _PyGC_REFS_UNTRACKED
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
PyObject *op;
PyGC_Head *g;
if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
return PyErr_NoMemory();
// 为 对象本身+PyGC_Head申请内存, 注意分配的size
g = (PyGC_Head *)PyObject_MALLOC(
sizeof(PyGC_Head) + basicsize);
if (g == NULL)
return PyErr_NoMemory();
// 初始化 GC_UNTRACED
g->gc.gc_refs = GC_UNTRACKED;
generations[0].count++; /* number of allocated GC objects */
// 如果大于阈值, 执行分代回收
if (generations[0].count > generations[0].threshold &&
enabled &&
generations[0].threshold &&
!collecting &&
!PyErr_Occurred()) {
collecting = 1;
collect_generations();
collecting = 0;
}
op = FROM_GC(g);
return op;
}
注意, FROM_GC和AS_GC用于 PyObject_HEAD <=> PyGC_HEAD地址相互转换
// => Modules/gcmodule.c
/* Get an object's GC head */
#define AS_GC(o) ((PyGC_Head *)(o)-1)
/* Get the object given the GC head */
#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
// => objimpl.h
#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
将list对象放到这个对象链表中
// => listobject.c
PyObject *
PyList_New(Py_ssize_t size)
{
PyListObject *op;
op = PyObject_GC_New(PyListObject, &PyList_Type);
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
// => _PyObject_GC_TRACK
// objimpl.h
// 加入到可收集对象链表中
#define _PyObject_GC_TRACK(o) do { \
PyGC_Head *g = _Py_AS_GC(o); \
if (g->gc.gc_refs != _PyGC_REFS_UNTRACKED) \
Py_FatalError("GC object already tracked"); \
g->gc.gc_refs = _PyGC_REFS_REACHABLE; \
g->gc.gc_next = _PyGC_generation0; \
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
g->gc.gc_prev->gc.gc_next = g; \
_PyGC_generation0->gc.gc_prev = g; \
} while (0);
将对象链表中摘除
// Objects/listobject.c
static void
list_dealloc(PyListObject *op)
{
Py_ssize_t i;
PyObject_GC_UnTrack(op);
.....
}
// => PyObject_GC_UnTrack => _PyObject_GC_UNTRACK
// 对象销毁的时候
#define _PyObject_GC_UNTRACK(o) do { \
PyGC_Head *g = _Py_AS_GC(o); \
assert(g->gc.gc_refs != _PyGC_REFS_UNTRACKED); \
g->gc.gc_refs = _PyGC_REFS_UNTRACKED; \
g->gc.gc_prev->gc.gc_next = g->gc.gc_next; \
g->gc.gc_next->gc.gc_prev = g->gc.gc_prev; \
g->gc.gc_next = NULL; \
} while (0);
垃圾回 例:
第⼀步:创建对象age=22,并将对象添加到refchain链表中。
第⼆步:创建对象num_list = [11,22],并将列表对象添加到 refchain和generations 0代中。
第三步:新创建对象使generations 0链表上的对象数量⼤于阈值700,对链表上的对象进⾏扫描检查。
当0代⼤于阈值后,底层不是直接扫描0代,⽽是先判断2、1代是否也超过了阈值。
- 如果2、1代未达到阈值,则扫描0代,并让1代的 count + 1 。
- 如果2代已达到阈值,则将2、1、0三个链表拼接起来进⾏全扫描,并将2、1、0代的count重置为0。
- 如果1代已达到阈值,则讲1、0两个链表拼接起来进⾏扫描,并将所有1、0代的count重置为0。
PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
// 执行分配
....
generations[0].count++; /* number of allocated GC objects */ //增加一个
if (generations[0].count > generations[0].threshold && // 发现大于预支了
enabled &&
generations[0].threshold &&
!collecting &&
!PyErr_Occurred())
{
collecting = 1;
collect_generations(); // 执行收集
collecting = 0;
}
op = FROM_GC(g);
return op;
}
=> collect_generations
static Py_ssize_t
collect_generations(void)
{
int i;
Py_ssize_t n = 0;
/* Find the oldest generation (highest numbered) where the count
* exceeds the threshold. Objects in the that generation and
* generations younger than it will be collected. */
// 从最老的一代, 开始回收
for (i = NUM_GENERATIONS-1; i >= 0; i--) { // 遍历所有generation
if (generations[i].count > generations[i].threshold) { // 如果超过了阈值
/* Avoid quadratic performance degradation in number
of tracked objects. See comments at the beginning
of this file, and issue #4074.
*/
if (i == NUM_GENERATIONS - 1
&& long_lived_pending < long_lived_total / 4)
continue;
n = collect(i); // 执行收集
break; // notice: break了
}
}
return n;
}
gcmodule.c
/* This is the main function. Read this to understand how the
* collection process works. */
static Py_ssize_t
collect(int generation)
{
// 第1步: 将所有比 当前代 年轻的代中的对象 都放到 当前代 的对象链表中
/* merge younger generations with one we are currently collecting */
for (i = 0; i < generation; i++) {
gc_list_merge(GEN_HEAD(i), GEN_HEAD(generation));
}
// 第2步
update_refs(young);
// 第3步
subtract_refs(young);
// 第4步
gc_list_init(&unreachable);
move_unreachable(young, &unreachable);
// 第5步
/* Move reachable objects to next generation. */
if (young != old) {
if (generation == NUM_GENERATIONS - 2) {
long_lived_pending += gc_list_size(young);
}
gc_list_merge(young, old);
}
else {
/* We only untrack dicts in full collections, to avoid quadratic
dict build-up. See issue #14775. */
untrack_dicts(young);
long_lived_pending = 0;
long_lived_total = gc_list_size(young);
}
// 第6步
delete_garbage(&unreachable, old);
}
将所有比 当前代 年轻的代中的对象 都放到 当前代 的对象链表中
// => gc_list_merge
// 执行拷贝而已
/* append list `from` onto list `to`; `from` becomes an empty list */
static void
gc_list_merge(PyGC_Head *from, PyGC_Head *to)
{
PyGC_Head *tail;
assert(from != to);
if (!gc_list_is_empty(from)) {
tail = to->gc.gc_prev;
tail->gc.gc_next = from->gc.gc_next;
tail->gc.gc_next->gc.gc_prev = tail;
to->gc.gc_prev = from->gc.gc_prev;
to->gc.gc_prev->gc.gc_next = to;
}
// 清空
gc_list_init(from);
}
=>
static void
gc_list_init(PyGC_Head *list)
{
list->gc.gc_prev = list;
list->gc.gc_next = list;
}
对拼接起来的链表在进⾏扫描时,主要就是剔除循环引⽤和销毁垃圾,详细过程为:
- 扫描链表,把每个对象的引⽤计数器拷⻉⼀份并保存到
gc_refs中,保护原引⽤计数器。
//遍历对象链表, 将每个对象的gc.gc_ref值设置为ob_refcnt
// => gcmodule.c
static void
update_refs(PyGC_Head *containers)
{
PyGC_Head *gc = containers->gc.gc_next;
for (; gc != containers; gc = gc->gc.gc_next) {
assert(gc->gc.gc_refs == GC_REACHABLE);
gc->gc.gc_refs = Py_REFCNT(FROM_GC(gc));
/* Python's cyclic gc should never see an incoming refcount
* of 0: if something decref'ed to 0, it should have been
* deallocated immediately at that time.
* Possible cause (if the assert triggers): a tp_dealloc
* routine left a gc-aware object tracked during its teardown
* phase, and did something-- or allowed something to happen --
* that called back into Python. gc can trigger then, and may
* see the still-tracked dying object. Before this assert
* was added, such mistakes went on to allow gc to try to
* delete the object again. In a debug build, that caused
* a mysterious segfault, when _Py_ForgetReference tried
* to remove the object from the doubly-linked list of all
* objects a second time. In a release build, an actual
* double deallocation occurred, which leads to corruption
* of the allocator's internal bookkeeping pointers. That's
* so serious that maybe this should be a release-build
* check instead of an assert?
*/
assert(gc->gc.gc_refs != 0);
}
}
- 再次扫描链表中的每个对象,并检查是否存在循环引⽤,如果存在则让各⾃的
gc_refs减 1 。
/* A traversal callback for subtract_refs. */
static int
visit_decref(PyObject *op, void *data)
{
assert(op != NULL);
// 判断op指向的对象是否是被垃圾收集监控的, 对象的type对象中有Py_TPFLAGS_HAVE_GC符号
if (PyObject_IS_GC(op)) {
PyGC_Head *gc = AS_GC(op);
/* We're only interested in gc_refs for objects in the
* generation being collected, which can be recognized
* because only they have positive gc_refs.
*/
assert(gc->gc.gc_refs != 0); /* else refcount was too small */
if (gc->gc.gc_refs > 0)
gc->gc.gc_refs--; // -1
}
return 0;
}
/* Subtract internal references from gc_refs. After this, gc_refs is >= 0
* for all objects in containers, and is GC_REACHABLE for all tracked gc
* objects not in containers. The ones with gc_refs > 0 are directly
* reachable from outside containers, and so can't be collected.
*/
static void
subtract_refs(PyGC_Head *containers)
{
traverseproc traverse;
PyGC_Head *gc = containers->gc.gc_next;
// 遍历链表
for (; gc != containers; gc=gc->gc.gc_next) {
// 与特定的类型相关, 得到类型对应的traverse函数
traverse = Py_TYPE(FROM_GC(gc))->tp_traverse;
// 调用
(void) traverse(FROM_GC(gc),
(visitproc)visit_decref, // 回调形式传入
NULL);
}
}
static int
dict_traverse(PyObject *op, visitproc visit, void *arg)
{
Py_ssize_t i = 0;
PyObject *pk;
PyObject *pv;
// 遍历所有键和值
while (PyDict_Next(op, &i, &pk, &pv)) {
Py_VISIT(pk);
Py_VISIT(pv);
}
return 0;
}
遍历容器对象里面的所有对象, 通过visit_decref将这些对象的引用计数都-1,
- 再次扫描链表,将
gc_refs为 0 的对象移动到unreachable链表中;不为0的对象升级到下⼀代链表中。
/* Move the unreachable objects from young to unreachable. After this,
* all objects in young have gc_refs = GC_REACHABLE, and all objects in
* unreachable have gc_refs = GC_TENTATIVELY_UNREACHABLE. All tracked
* gc objects not in young or unreachable still have gc_refs = GC_REACHABLE.
* All objects in young after this are directly or indirectly reachable
* from outside the original young; and all objects in unreachable are
* not.
*/
static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
PyGC_Head *gc = young->gc.gc_next;
/* Invariants: all objects "to the left" of us in young have gc_refs
* = GC_REACHABLE, and are indeed reachable (directly or indirectly)
* from outside the young list as it was at entry. All other objects
* from the original young "to the left" of us are in unreachable now,
* and have gc_refs = GC_TENTATIVELY_UNREACHABLE. All objects to the
* left of us in 'young' now have been scanned, and no objects here
* or to the right have been scanned yet.
*/
while (gc != young) {
PyGC_Head *next;
// 对于root object,
if (gc->gc.gc_refs) {
/* gc is definitely reachable from outside the
* original 'young'. Mark it as such, and traverse
* its pointers to find any other objects that may
* be directly reachable from it. Note that the
* call to tp_traverse may append objects to young,
* so we have to wait until it returns to determine
* the next object to visit.
*/
PyObject *op = FROM_GC(gc);
traverseproc traverse = Py_TYPE(op)->tp_traverse;
assert(gc->gc.gc_refs > 0);
// 设置其gc->gc.gc_refs = GC_REACHABLE
gc->gc.gc_refs = GC_REACHABLE;
// 注意这里逻辑, visit_reachable, 意图是?
(void) traverse(op,
(visitproc)visit_reachable,
(void *)young);
next = gc->gc.gc_next;
if (PyTuple_CheckExact(op)) {
_PyTuple_MaybeUntrack(op);
}
}
// 有效引用计数=0, 非root对象, 移动到unreachable链表中
else {
/* This *may* be unreachable. To make progress,
* assume it is. gc isn't directly reachable from
* any object we've already traversed, but may be
* reachable from an object we haven't gotten to yet.
* visit_reachable will eventually move gc back into
* young if that's so, and we'll see it again.
*/
next = gc->gc.gc_next;
gc_list_move(gc, unreachable);
gc->gc.gc_refs = GC_TENTATIVELY_UNREACHABLE;
}
gc = next;
}
}
将存活对象放入下一代
/* Move reachable objects to next generation. */
if (young != old) {
if (generation == NUM_GENERATIONS - 2) {
long_lived_pending += gc_list_size(young);
}
gc_list_merge(young, old);
}
else {
/* We only untrack dicts in full collections, to avoid quadratic
dict build-up. See issue #14775. */
untrack_dicts(young);
long_lived_pending = 0;
long_lived_total = gc_list_size(young);
}
-
处理
unreachable链表中的对象的 析构函数 和 弱引⽤,不能被销毁的对象升级到下⼀代链表,能销毁的保留在此链表。
析构函数:指的就是那些定义了__del__⽅法的对象,需要执⾏后再进⾏销毁处理。弱引⽤:不会增加引用计数,当对象没有强引用时会被回收
-
最后将
unreachable中的每个对象回收(销毁或放入free_list)并在refchain链表中移除。
gcmoudle.c
static int
gc_list_is_empty(PyGC_Head *list)
{
return (list->gc.gc_next == list);
}
/* Break reference cycles by clearing the containers involved. This is
* tricky business as the lists can be changing and we don't know which
* objects may be freed. It is possible I screwed something up here.
*/
static void
delete_garbage(PyGC_Head *collectable, PyGC_Head *old)
{
inquiry clear;
// 遍历
while (!gc_list_is_empty(collectable)) {
PyGC_Head *gc = collectable->gc.gc_next;
// 得到对象
PyObject *op = FROM_GC(gc);
assert(IS_TENTATIVELY_UNREACHABLE(op));
if (debug & DEBUG_SAVEALL) {
PyList_Append(garbage, op);
}
else {
// 清引用
if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
Py_INCREF(op);
// 这个操作会调整container对象中每个引用所有对象的引用计数, 从而完成打破循环的最终目标
clear(op);
Py_DECREF(op);
}
}
// 重新送回到reachable链表.
// 原因: 在进行clear动作, 如果成功, 会把自己从垃圾收集机制维护的链表中摘除, 由于某些原因, 对象可能在clear的时候, 没有成功完成必要动作, 还不能被销毁, 所以放回去
if (collectable->gc.gc_next == gc) {
/* object is still alive, move it, it may die later */
gc_list_move(gc, old);
gc->gc.gc_refs = GC_REACHABLE;
}
}
}
=> 来看下, list的clear
static int
list_clear(PyListObject *a)
{
Py_ssize_t i;
PyObject **item = a->ob_item;
if (item != NULL) {
/* Because XDECREF can recursively invoke operations on
this list, we make it empty first. */
i = Py_SIZE(a);
Py_SIZE(a) = 0;
a->ob_item = NULL;
a->allocated = 0;
while (--i >= 0) {
// 减引用
Py_XDECREF(item[i]);
}
PyMem_FREE(item);
}
/* Never fails; the return value can be ignored.
Note that there is no guarantee that the list is actually empty
at this point, because XDECREF may have populated it again! */
return 0;
}
// e.g. 处理list3, 调用其list_clear, 减少list4的引用计数, list4.ob_refcnt=0, 引发对象销毁, 调用list4的list_dealloc
static void
list_dealloc(PyListObject *op)
{
Py_ssize_t i;
PyObject_GC_UnTrack(op); // 从可收集对象链表中去除, 会影响到list4所引用所有对象的引用计数, => list3.refcnt=0, list3的销毁动作也被触发
Py_TRASHCAN_SAFE_BEGIN(op)
if (op->ob_item != NULL) {
/* Do it backwards, for Christian Tismer.
There's a simple test case where somehow this reduces
thrashing when a *very* large list is created and
immediately deleted. */
i = Py_SIZE(op);
while (--i >= 0) {
Py_XDECREF(op->ob_item[i]);
}
PyMem_FREE(op->ob_item);
}
if (numfree < PyList_MAXFREELIST && PyList_CheckExact(op))
free_list[numfree++] = op;
else
Py_TYPE(op)->tp_free((PyObject *)op);
Py_TRASHCAN_SAFE_END(op)
}
4.缓存机制
由于反复的创建和销毁会使程序的执⾏效率变低,所以Python中引⼊了一种缓存机制。即引⽤计数器为0时,不会真正销毁对象,⽽是将他放到名为 free_list 的链表中,之后会再创建对象时不再重新开辟内存,⽽是从free_list中捞出一个对象,重置内部的值来使⽤。
不同数据类型的free_list是不一样的:
- float类型,维护的free_list链表最多可缓存100个float对象。
v1 = 3.14 # 开辟内存来存储float对象,并将对象添加到refchain链表。
print( id(v1) ) # 内存地址:4436033488
del v1 # 引⽤计数器-1,如果为0则在rechain链表中移除,不销毁对象,⽽是将对象添加到float的free_list.
v2 = 9.999 # 优先去free_list中获取对象,并重置为9.999,如果free_list为空才重新开辟内存。
print( id(v2) ) # 内存地址:4436033488
# 注意:引⽤计数器为0时,会先判断free_list中缓存个数是否满了,未满则将对象缓存,已满则直接将对象销毁。
- list类型,维护的free_list数组最多可缓存80个list对象。
v1 = [111]
print(id(v1)) # 2780926292480
del v1
v2 = [555]
print(id(v2))# 2780926292480
- tuple类型,维护⼀个free_list数组且数组容量20,数组中元素可以是链表且每个链表最多可以容纳2000个元组对象。元组的free_list数组在存储数据时,是按照元组可以容纳的个数为索引找到free_list数组中对应的链表,并添加到链表中。
v1 = (111, 2222)
print(id(v1)) # 2780926292480
del v1
v2 = ('zzz', 6666)
print(id(v2)) # 2780926292480
dict类型,维护的free_list数组最多可缓存80个dict对象。
v1 = {'a': 222}
print(id(v1)) # 2780926292480
del v1
v2 = {'aa': 666}
print(id(v2)) # 2780926292480
另外,int和str类型,不是基于free_list,而是在python解释器启动时,就创建了一个小数据池。
- int类型,维护⼀个
small_ints链表保存常⻅数据,⼩数据池范围:-5 <= value < 257,当使⽤这个范围的整数时,不会重新开辟内存。
v1 = 38 # 去⼩数据池small_ints中获取38整数对象,将对象添加到refchain并让引⽤计数器+1。
print( id(v1)) #内存地址:4514343712
v2 = 38 # 去⼩数据池small_ints中获取38整数对象,将refchain中的对象的引⽤计数器+1。
print( id(v2) ) #内存地址:4514343712
# 注意:在解释器启动时候-5~256就已经被加⼊到small_ints链表中且引⽤计数器初始化为1,代码中使⽤的值时直接去small_ints中拿来⽤并将引⽤计数器+1即可。另外,small_ints中的数据引⽤计数器永远不会为0(初始化时就设置为1了),所以也不会被销毁
- str类型,维护
unicode_latin1[256]链表,内部将所有的ascii字符缓存起来,使⽤时就不再重新创建。
v1 = "A"
print( id(v1) ) # 输出:4517720496
del v1
v2 = "A"
print( id(v1) ) # 输出:4517720496
# 除此之外,Python内部还对字符串做了驻留机制,针对只含有字⺟、数字、下划线的字符串(⻅源码Objects/codeobject.c),如果内存中已存在则不会重新在创建⽽是使⽤原来的地址⾥(不会像free_list那样⼀直在内存存活,只有内存中有才能被重复利⽤)。
v1 = "wupeiqi"
v2 = "wupeiqi"
print(id(v1) == id(v2)) # 输出:True
注意:python在不同版本和不同环境下,上述代码执行结果可能出现偏差,原因通常是 Python 内存分配器优化或特定代码块优化的结果。
CPython 解释器中主要的数据驻留规则:
| 数据类型 | 驻留条件 | 说明与示例 |
|---|---|---|
| 整数 | -5 到 256 | 这个范围内的整数被预先创建并缓存,全局唯一。a = 100; b = 100; a is b 返回 True。 |
| 字符串 | 长度為 0 或 1 | 所有空字符串和单字符字符串都会被驻留。 |
| 仅含字母、数字、下划线的标识符 | 类似于变量名的字符串,如 "hello_world"。 |
|
| 由乘法运算生成且长度有限制 | 规则较复杂,Python 版本不同,长度限制可能不同(如 20 或 4096),且字符串只能包含数字、字母、下划线。 | |
显式调用 sys.intern() |
可以强制驻留任意字符串,常用于性能优化场景,如处理大量重复的字典键。 | |
| 其他单例对象 | None, True, False, Ellipsis (...) |
这些对象在解释器中是全局唯一的。 |
| 空不可变容器 | 空元组 (), 空字符串 "" |
会被缓存。注意,空列表 [] 和空字典 {} 等可 |
- 代码块优化:Python 会对一个代码块(如一个模块、一个函数体、一个类定义或交互式环境中的单行命令)内的不可变对象进行一些优化,主要目的是提高执行效率和减少内存使用。如果在一个代码块内多次出现相同的浮点数字,解释器可能会使其指向同一个对象以节省内存。但这并非强制规定,行为可能因 Python 版本或具体代码上下文而异。
- 内存分配器优化(
Pymalloc):Python 对小对象(通常小于等于512字节)有自己的内存分配器。当一个浮点数对象被销毁后,其释放的内存可能会被紧接着创建的相同大小的新对象(未必是相同的浮点数值)立即重用。这会导致你观察到新对象的 ID(内存地址)与刚被销毁的对象的 ID 相同,但这与基于值的驻留是两回事。
参考资料:
浙公网安备 33010602011771号