Python的垃圾回收机制以引⽤计数器为主、分代码回收和标记清除为辅

1.refchai链表

在Python的C源码中有⼀个名为refchain的环状双向链表,在Python程序中每创建1个对象,就会将其加入此链表。

city = '四川'  内部会创建一个结构体,包含【上一个对象、下一个对象、类型、引用计数、值、其他特有属性】
num= 123

image-20250927180930278

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;

这两个结构体PyObjectPyVarObject是基本结构,他们是各数据类型公共部分。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

image-20250927185301209

image-20250927185311405

  • 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_GCAS_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],并将列表对象添加到 refchaingenerations 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 (...) 这些对象在解释器中是全局唯一的。
空不可变容器 空元组 (), 空字符串 "" 会被缓存。注意,空列表 [] 和空字典 {} 等可
  1. 代码块优化:Python 会对一个代码块(如一个模块、一个函数体、一个类定义或交互式环境中的单行命令)内的不可变对象进行一些优化,主要目的是提高执行效率和减少内存使用。如果在一个代码块内多次出现相同的浮点数字,解释器可能会使其指向同一个对象以节省内存。但这并非强制规定,行为可能因 Python 版本或具体代码上下文而异
  2. 内存分配器优化(Pymalloc:Python 对小对象(通常小于等于512字节)有自己的内存分配器。当一个浮点数对象被销毁后,其释放的内存可能会被紧接着创建的相同大小的新对象(未必是相同的浮点数值)立即重用。这会导致你观察到新对象的 ID(内存地址)与刚被销毁的对象的 ID 相同,但这与基于值的驻留是两回事。

参考资料:

Python垃圾回收.pdf

https://wklken.me/posts/2015/09/29/python-source-gc.html

posted on 2025-09-28 00:12  莫名丨其妙  阅读(14)  评论(0)    收藏  举报