Python源码之内存管理机制(二)---垃圾回收机制

文章目录

  • 1、引用计数
    • 1.1 循环引用问题
  • 2、三色标记机制
  • 3、垃圾收集机制
    • 3.1 对象集合--双向链表
    • 3.2 分代回收机制
    • 3.3 标记-清除机制
      • 3.3.1 寻找root object的集合
      • 3.3.2 垃圾标记
      • 3.3.3 垃圾回收
  • 4、垃圾收集机制总结

1、引用计数

  在之前的文章中我们都有看到各种与引用计数相关的东西,Python和Java,c#一样也在语言层面实现了动态的内存管理机制,这一点我们很清楚,这就意味着我们从繁琐的手动内存管理中解放了,将内存的释放和各种管理交给Python自己完成,我们可以看到在前面的内存管理架构中,手动管理内存虽然有很大的灵活性但是非常繁琐。
  在Python中,大多数对象的生命周期都是通过引用计数来完成的,它是一种最简单,而且最容易实现的一种垃圾收集机制。它在对象进行内存的分配,释放的时候都会有管理引用计数的操作,引用计数有一个非常明显的优点,就是实时性很强,对于任何一块内存,一旦没有指向它的引用,它就会立即被回收。同时它也有致命的一个弱点,就是无法处理循环引用的情况。

  • 1.1 循环引用问题

  当一个对象的引用被创建或者被复制的时候,引用计数会加1,同样当一个对象的引用被销毁时,它的引用计数就会减1. 当一个对象的也能用计数减少为0的时候,就意味着,没有人引用这个对象,这个对系那个所占用的内存就需要被释放。我们来考虑一下下面的情况:

l1 = []
l2 = []

l1.append(l2)
l2.append(l1)

del l1, l2

可以看到这两个对象没有被任何外部变量所引用,它们只是相互引用,因此它们的引用计数都不为0,这就意味着即使没有外部变量引用它们,但是由于引用计数不等于0,它们所占用的内存空间就没办法被释放。这是一个严重的问题,这和内存泄露没有什么分别。虽然这个问题可以在语言层面去避免,但是这就要求开发者必须自己精心设计代码结构从而避免这个问题,如此一来那为什么我不去选择一种不存在这个问题的语言呢?因而Python为了解决这个问题,引入了标记–清除和分代回收的技术。

2、三色标记机制

  在垃圾收集的过程中,一般都分为两个过程:其一是垃圾的检测,就是从已经分配的内存中寻找出哪些是可以回收的,哪些是不可以回收的;其二就是垃圾的回收,就是让系统重新掌握被检测出来的可回收内存块。 我们接下来就看看标记–清除是如何工作的。
  标记–清除的过程如下:
1、寻找根对象(root object)的集合,所谓的root object就是一些全局引用和函数栈的引用。这些引用所用的对象是不可被删除的,而这个root object集合也是垃圾检测动作的起点
2、从root object集合出发,沿着root object集合中的每一个引用,如果能到达某个对象A,则称A是可达的(reachable),可达的对象也不可被删除。这个阶段就是垃圾检测阶段
3、当垃圾检测阶段结束后,所有的对象分为了可达的(reachable)和不可达的(unreachable)。而所有可达对象都必须予以保留,而不可达对象所占用的内存将被回收。

  现在我们来看看三色标记模型是怎么样被建立起来的。首先在触发回收动作之前,我们将系统中所有分配的对象以及对象之间的引用组成一个有向图,每个对象为有向图的结点,对象之间的引用为有向图的边。在垃圾回收动作前假设所有的对象都是不可达的,有向图中的所有结点被标记为白色。在某个时刻,垃圾回收开始,这时沿着root object集合中的某个引用链,到达了对象A,表示它可达,我们将它标记为灰色,它表示它所包含的引用还没有被检查。当A所包含的引用被检查了后,A被标记为黑色,表示它所包含的引用已经全部被检查了,这时A所包含的引用的对象被标记为灰色。因此在某个时刻它可能的状态如下图所示:
Python源码之内存管理机制(二)---垃圾回收机制_第1张图片

3、垃圾收集机制

  我们知道在Python的内存管理中,主要还是靠引用计数,而像标记–清除和分代回收机制只是为了解决循环引用问题而引入的辅助技术。因此在Python的垃圾回收机制中,只有存在循环引用的对象才会被跟踪,对于像整数类型,字符串这样的类型它们内部不存在引用其他对象的情况。那么哪些对象会出现循环引用呢?例如,list,dict,set,instance等容器。因此垃圾回收机制的开销仅仅作用于这些对象中,那么要对他们进行检测收集就必须跟踪这些对象的引用情况,也就是说我们必须将这些对象采用某种集合集中起来,以便后续的检测,Python采用了一个双向链表来组合它们,当这些对象被创建时,就会被插入到链表中。

  • 3.1 对象集合–双向链表

  如果一个可收集的对象要被垃圾收集机制跟踪就必须将它加入到链表中去,因此这个对象内必须要包含某些信息被作用于这个链表。可是我们知道在一个对象中分为PyObject_HEAD头部信息和数据本身,因此这个额外的信息在哪里呢?这个信息就在头部之前被定义为:PyGC_Head

// objimpl.h
typedef union _gc_head {
    struct {
        union _gc_head *gc_next;
        union _gc_head *gc_prev;
        Py_ssize_t gc_refs;
    } gc;
    double dummy;  /* force worst-case alignment */
} PyGC_Head;

// modules/gcmodule.c
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 *
_PyObject_GC_Malloc(size_t basicsize)
{
    return _PyObject_GC_Alloc(0, basicsize);
}

static PyObject *
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
    PyObject *op;
    PyGC_Head *g;
    size_t size;
    if (basicsize > PY_SSIZE_T_MAX - sizeof(PyGC_Head))
        return PyErr_NoMemory();
    size = sizeof(PyGC_Head) + basicsize;
    if (use_calloc)
        g = (PyGC_Head *)PyObject_Calloc(1, size);
    else
        g = (PyGC_Head *)PyObject_Malloc(size);
    if (g == NULL)
        return PyErr_NoMemory();
    g->gc.gc_refs = 0;
    // 设置gc_refs的值为GC_UNTRACKED
    _PyGCHead_SET_REFS(g, GC_UNTRACKED);
    // 将0代链表的数量加1
    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;
}

  我们可以看到在创建对象时,在_PyObject_GC_New()函数中调用了_PyObject_GC_Malloc()最后又调用了_PyObject_GC_Alloc()。Python在为可收集的对象申请内存空间时也为Py_GC_HEAD申请了内存,而且在对象本身之前,因此其内存结构就很明显了。
  在垃圾收集机制运行过程中,会根据一个对象的PyGC_Head地址来计算获得PyObject_HEAD地址,同样有时也需要根据PyObject_HEAD地址或计算获得PyGC_Head的地址。

//gcmodule.c
//AS_GC,根据PyObject_HEAD得到PyGC_Head
#define AS_GC(o) ((PyGC_Head *)(o)-1)
//FROM_GC,从PyGC_Head那里得到PyObject_HEAD
#define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
//objimpl.h
#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)

  从前面关于PyGC_Head的定义中我们知道,有两个指针用于创建可收集对象的链表,但是在上面的代码中我们并没有看到有将这个对象链入到链表中的操作,那么这个操作是在什么时候完成的呢?这个操作是在创建对象的最后一步,从前面我们解析Python内建对象的时候在创建对象的最后一步会有一个函数调用–PyObject_GC_Track(),这个函数中为一个宏,这个宏定义如下,就是用来追踪对象的,也就是将它加入到链表中去,我们来看看原型:

// objimpl.h
#define _PyObject_GC_TRACK(o) do { \
    PyGC_Head *g = _Py_AS_GC(o); \
    if (_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED) \
        Py_FatalError("GC object already tracked"); \
    _PyGCHead_SET_REFS(g, _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);

在将这个对象链入到链表中后,垃圾收集机制就会作用于这个链表,从而创建的对象就在追踪之下了。Python不仅可以将一个刚创建的对象链入到链表中,还可以从链表中摘除,摘除动作就发生在对象被销毁的时候。

  • 3.2 分代回收机制

  在人们对垃圾回收机制的研究过程中发现,不管对于什么语言,不同对象的生命周期会存在不同,有的对象所占的内存块的生命周期很短,而有的内存块的生命周期则很长,甚至可能从程序的开始持续到程序结束。这两者的比例大概在80~90%。试想这样一个问题,之前我们讲了关于标记-清除的机制,现在所有的可收集的对象都在这条双向链表中,每次在进行垃圾回收的时候都要进行垃圾检测的操作将所有的对象都检测一遍,实际上按照前面我们所说的研究结果,有很多对象是比较稳定的,没有必要每次都对它进行检测,因为每次检测带来的额外开销都是不小的,因此我们可以采用一种以空间换取时间的策略来解决这个问题。
  其主要思想是,我们根据对象的存活时间将其分为不同的集合,每一个集合称为一个 “代” ,垃圾收集的频率随着 “代” 的增加而减少,换句话说就是,存活时间越长的对象就越不可能成为垃圾,应该尽量减少收集的频率。而我们通过每个对象经历了几次垃圾收集动作来衡量其存活时间,如果一个对象经历的垃圾收集的动作越多,它的存活时间就越长。在Python中,一个 “代” 就是一个链表,并且Python一共划分了三个代,因此在内部存在三个链表,每一个链表都代表一个代,在前面讲过的可收集对象的链表,为了支持分代的机制,只需要额外一个表头

// 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
#define GEN_HEAD(n) (&generations[n].head)

/* linked lists of container objects */
static struct gc_generation generations[NUM_GENERATIONS] = {
    /* PyGC_Head,                               threshold,      count */
    {{{GEN_HEAD(0), GEN_HEAD(0), 0}},           700,            0},
    {{{GEN_HEAD(1), GEN_HEAD(1), 0}},           10,             0},
    {{{GEN_HEAD(2), GEN_HEAD(2), 0}},           10,             0},
};
PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);

  Python内部维护了三个gc_generation结构的数组,通过这个数组控制了三条可收集对象链表,这就是python中用于分代垃圾收集的三个"代"。对于每个gc_generation,内部的count域是用来存储在这条链表中有多少可收集的对象,也就是在这一代有多少可收集的对象。在_PyObject_GC_Alloc()中,会有一个generation[0].count++的操作,也就是说每次创建一个可收集的对象时,都会先将其数量先加1然再将它加入到0代链表中去。在gc_generation中的threshold是用来表示此代链表中的对象个数的阈值,一旦链表的对象个数超过这个阈值就会触发收集操作。我们从数组的定义中可以看到,0代默认为700,1代和2代默认为10.这个收集操作是通过collect_generation()来实现的,在_PyObject_GC_Alloc()函数中可以看到当0代的对象数量超过这个阈值便会调用collect_generation()来执行收集动作。

// gcmodule.c
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--) {
        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_with_callback(i);
            break;
        }
    }
    return n;
}

  在这里虽然说是由第0代触发了垃圾收集的操作,但是在代码注释中我们看到在进行收集操作时,Python会寻找满足条件的最老的那一代对象并对它进行回收,并且会对比它更年轻的代也进行回收动作,这个条件就是for循环的意义。但是可以看到在寻找到最老的那一代并进行收集后就直接break了,那么更年轻一代的回收在哪里进行的呢?猜测可能在这个collect_with_callback(i)函数调用中。我们一会儿再聊它。

  • 3.3 标记-清除机制

  Python在对某一代的对象链表进行垃圾收集的之前实际上要做一些准备工作的,这个准备工作就是将更年轻的一代链表连接到当前所要收集的链表之后。也就是说假如现在收集的是第2代对象的链表,那么就需要将1代和0代整个连接到其后。这个操作是通过gc_list_merge()函数来实现的,我们看看它的定义:

// gcmodule.c
/* 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);
}

其实将两条链表合并就是改改指针的操作。
  接下来我们就来看看垃圾收集机制如何解决循环引用的问题,我们来看一个示例演示了循环引用的例子,然后用图来表示它们的引用关系:

list1 = []
list2 = []

list1.append(list2)
list2.append(list1)

a = list1

list3 = []
list4 = []
list3.append(list4)
list4.append(list3)

Python源码之内存管理机制(二)---垃圾回收机制_第2张图片

  • 3.3.1 寻找root object的集合

  我们知道,在使用标记-清除进行对象的回收时,必须先找到root object,那么在上面的图示中哪些属于root object呢?root object是不能被删除的对象,也就是说有可收集对象链表外的某个引用在引用这个对象,删除它会导致错误的行为,我们知道在上图中list1是不能被删除的对象。我们知道当两个对象它们的引用计数都为1,但是仅仅存在它们的循环引用,此时我们称它们的有效引用计数为0,这两个对象是需要被回收的。那么就需要从引用计数来获得它们的有效引用计数,也就是说必须要将它们的引用环给去除掉,那我们怎么将引用环给去除掉呢?可以采用这样的方式,假如两个对象为A和B,那么从A出发,由于它有一个对B的引用,则将B的引用计数减1;然后顺着引用达到B,发现它有一个对A的引用,那么同样会将A的引用减1,这样就完成了循环引用对象间环的删除。
  这样做虽然可以摘除引用环但是有个问题,假如对象A中有一个对对象C的引用,这个对象不在这个可收集对象链表中,如果将C的引用计数减1,最后A并没有被回收,C的引用计数被错误地减少1,这将导致未来的某个时刻对C的引用会出现悬空。因此我们需要在A没有被删除的某个时刻对C的引用计数复原,但是这样做复杂度太高,因此Python采用了另一种做法,就是并改动真正的引用计数,而是修改它的副本。对于副本,我们无论做什么样的改动,都不会影响对象生命周期的维护,因为这个副本的唯一作用就是寻找root object集合,而这个副本就是PyGC_Head中的gc.gc_ref。在垃圾回收的第一步,就是遍历可收集对象链表,将每个对象的gc.gc_ref的值设置为其ob_refcnt的值。

// gcmodule.c

#define _PyGCHead_SET_REFS(g, v) do { \
    (g)->gc.gc_refs = ((g)->gc.gc_refs & ~_PyGC_REFS_MASK) \
        | (((size_t)(v)) << _PyGC_REFS_SHIFT);             \
    } while (0)

static void
update_refs(PyGC_Head *containers)
{
    PyGC_Head *gc = containers->gc.gc_next;
    for (; gc != containers; gc = gc->gc.gc_next) {
        assert(_PyGCHead_REFS(gc) == GC_REACHABLE);
        // 将对象的ob_refcnt的值赋给gc_refs
        // Py_REFCNT()取出ob_recnt的值
        _PyGCHead_SET_REFS(gc, Py_REFCNT(FROM_GC(gc)));
        assert(_PyGCHead_REFS(gc) != 0);
    }
}

上面的代码中,我们看到,这里使用for循环将链表中所有对象遍历了一遍,并且将对象的引用计数取出赋值给gc_refs,赋值完成就可以进行引用环的解除。

// gcmodule.c
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 = Py_TYPE(FROM_GC(gc))->tp_traverse;
        (void) traverse(FROM_GC(gc),
                       (visitproc)visit_decref,
                       NULL);
    }
}

我们注意到里面有一个traverse,这个是和特定的container 对象有关的,在container对象的类型对象中定义。一般来说,traverse的动作就是遍历container对象中的每一个引用,然后对引用进行某种动作,而这个动作在subtract_refs中就是visit_decref,它以一个回调函数的形式传递到traverse操作中。

// gcmodule.c
static int
visit_decref(PyObject *op, void *data)
{
    assert(op != NULL);
    if (PyObject_IS_GC(op)) {
        PyGC_Head *gc = AS_GC(op);
        assert(_PyGCHead_REFS(gc) != 0); /* else refcount was too small */
        if (_PyGCHead_REFS(gc) > 0)
            _PyGCHead_DECREF(gc);
    }
    return 0;
}

当完成了引用环的摘除后,有些对象的引用计数仍不为0,也就是说这些对象就是root object对象。标记-清除就从这些对象开始往下遍历。

  • 3.3.2 垃圾标记

  在前面我们说在标记-清除的过程中,我们需要从root object集合出发沿着每一个引用去寻找不可达的对象做标记。然而实际上在这之前Python是先将这条链表分为两部分,其中一条链表是维护root object集合,为root链表;而另一条链表维护剩下的对象,称为unreachable链表。明显现在的这条unreachable链表并非真正的不可达的链表,里面完全存在被root对象直接或间接引用的对象,因此对于这样的对象我们就需要把它移动到root链表中,这就是标记,当这个操作被完成后,unreachable链表中的对象才是真正的垃圾对象,这时我们的回收动作就可以作用于这条 “垃圾链表” 。原始链表的一分为二是通过move_unreachable()调用来实现的:

// gcmodule.c
static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
    PyGC_Head *gc = young->gc.gc_next;
    while (gc != young) {
        PyGC_Head *next;
		// 如果是root object
        if (_PyGCHead_REFS(gc)) {
            PyObject *op = FROM_GC(gc);
            traverseproc traverse = Py_TYPE(op)->tp_traverse;
            assert(_PyGCHead_REFS(gc) > 0);
            // 将此对象设置为GC_REACHABLE标志
            _PyGCHead_SET_REFS(gc, GC_REACHABLE);
            (void) traverse(op,
                            (visitproc)visit_reachable,
                            (void *)young);
            next = gc->gc.gc_next;
            if (PyTuple_CheckExact(op)) {
                _PyTuple_MaybeUntrack(op);
            }
        }
        else {
        	// 非root object对象,移动到unreachable链表中
        	// 并对它进行GC_TENTATIVELY_UNREACHABLE的标记
            next = gc->gc.gc_next;
            // 真正移动的操作
            gc_list_move(gc, unreachable);
            _PyGCHead_SET_REFS(gc, GC_TENTATIVELY_UNREACHABLE);
        }
        gc = next;
    }
}

static int
visit_reachable(PyObject *op, PyGC_Head *reachable)
{
    if (PyObject_IS_GC(op)) {
        PyGC_Head *gc = AS_GC(op);
        const Py_ssize_t gc_refs = _PyGCHead_REFS(gc);

        if (gc_refs == 0) {
            _PyGCHead_SET_REFS(gc, 1);
        }
        else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) {
         
            gc_list_move(gc, reachable);
            _PyGCHead_SET_REFS(gc, 1);
        }
      
         else {
            assert(gc_refs > 0
                   || gc_refs == GC_REACHABLE
                   || gc_refs == GC_UNTRACKED);
         }
    }
    return 0;
}

  在move_unreachable中,沿着可收集对象链表依次向前,并检查其PyGC_Head.gc.gc_ref值,我们发现这里的动作是遍历链表,而并非从root object集合出发,遍历引用链。这会导致一个微妙的结果,即当检查到一个gc_ref为0的对象时,我们并不能立即断定这个对象就是垃圾对象。因为在这个对象之后的对象链表上,也许还会遇到一个root object,而这个root object引用该对象。所以这个对象只是一个可能的垃圾对象,因此我们才要将其标志为GC_TENTATIVELY_UNREACHABLE,但是还是通过gc_list_move将其搬到了unreachable链表中。
  当在move_unreachable中遇到一个gc_refs不为0的对象A时,显然,A是root object或者是从某个root object开始可以引用到的对象,而A所引用的所有对象也都是不可回收的对象。因此在代码中,我们看到会再次调用与特定对象相关的transverse操作,依次对A所引用的对象调用visit_reachable。在visit_reachable处我们发现,如果A所引用的对象之前曾被标注为GC_TENTATIVELY_UNREACHABLE,那么现在A可以访问到它,意味着它也是一个不可回收的对象,所以python会再次从unreachable链表中将其搬回到原来的链表。注意:这里的reachable,就是move_unreachable中的young,也就是我们所谓的root object链表。python还会将其gc_refs设置为1,表示该对象是一个不可回收对象。同样,我们看到对A所引用的gc_refs为0的对象,其gc_refs也被设置成了1。想一想这是什么对象呢?显然它就是在链表move_unreachable操作中还没有访问到的对象,这样python就直接掐断了之后move_unreachable访问它时将其移动到unreachable链表的诱因。
  当move_unreachable完成之后,最初的一条链表就被切分成了两条链表,在unreachable链表中,就是我们发现的垃圾对象,是垃圾回收的目标。但是等一等,在unreachable链表中,所有的对象都可以安全回收吗?其实,垃圾回收在清理对象的时候,默认是会清理的,但是一旦当我们定义了函数__del__,那么在清理对象的时候就会调用这个__del__方法,因此也叫析构函数,这是python为开发人员提供的在对象被销毁时进行某些资源释放的Hook机制。在python3中,即使我们重写了也没事,因为python会把含有__del__函数的PyInstanceObject对象都统统移动到一个名为garbage的PyListObject对象中。

  • 3.3.3 垃圾回收

  我们通过之前的文字详细介绍了摘除循环引用的算法,Python通过一个引用计数的副本gc_refs来模拟整个过程。现在我们啦看看如何对ob_refcnt这个对象真正的引用计数进行对象的销毁。

// gcmodule.c
static int
gc_list_is_empty(PyGC_Head *list)
{
    return (list->gc.gc_next == list);
}

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);

        if (debug & DEBUG_SAVEALL) {
            PyList_Append(garbage, op);
        }
        else {
            if ((clear = Py_TYPE(op)->tp_clear) != NULL) {
                Py_INCREF(op);
                clear(op);
                Py_DECREF(op);
            }
        }
        if (collectable->gc.gc_next == gc) {
            /* object is still alive, move it, it may die later */
            gc_list_move(gc, old);
            _PyGCHead_SET_REFS(gc, GC_REACHABLE);
        }
    }
}

其中会调用container对象的类型对象中的tp_clear操作,这个操作会调整container对象中引用的对象的引用计数值,从而打破完成循环的最终目标。我们注意到,在delete_garbage中,有一些unreachable链表中的对象会被重新送回到reachable链表(即delete_garbage的old参数)中,这是由于进行clear动作时,如果成功进行,则通常一个对象会把自己从垃圾回收机制维护的链表中摘除(也就是这里的collectable链表)。由于某些原因,对象可能在clear动作时,没有成功完成必要的动作,从而没有将自己从collectable链表摘除,这表示对象认为自己还不能被销毁,所以python需要讲这种对象放回到reachable链表中。

4、垃圾收集机制总结

  我们介绍完各个细节后,我们现在来看看垃圾收集机制的整个全景,也就是collect()函数:

gcmodule.c
/* This is the main function.  Read this to understand how the
 * collection process works. */
static Py_ssize_t
collect(int generation, Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable,
        int nofail)
{
    int i;
    Py_ssize_t m = 0; /* # objects collected */
    Py_ssize_t n = 0; /* # unreachable objects that couldn't be collected */
    PyGC_Head *young; /* the generation we are examining */
    PyGC_Head *old; /* next older generation */
    PyGC_Head unreachable; /* non-problematic unreachable trash */
    PyGC_Head finalizers;  /* objects with, & reachable from, __del__ */
    PyGC_Head *gc;
    _PyTime_t t1 = 0;   /* initialize to prevent a compiler warning */

    struct gc_generation_stats *stats = &generation_stats[generation];

    if (debug & DEBUG_STATS) {
        PySys_WriteStderr("gc: collecting generation %d...\n",
                          generation);
        PySys_WriteStderr("gc: objects in each generation:");
        for (i = 0; i < NUM_GENERATIONS; i++)
            PySys_FormatStderr(" %zd",
                              gc_list_size(GEN_HEAD(i)));
        t1 = _PyTime_GetMonotonicClock();

        PySys_WriteStderr("\n");
    }

    if (PyDTrace_GC_START_ENABLED())
        PyDTrace_GC_START(generation);

    /* update collection and allocation counters */
    if (generation+1 < NUM_GENERATIONS)
        generations[generation+1].count += 1;
    for (i = 0; i <= generation; i++)
        generations[i].count = 0;

    /* merge younger generations with one we are currently collecting */
    // 将更加年轻的“代”的链表合并到当前正在收集处理的链表上去
    for (i = 0; i < generation; i++) {
        gc_list_merge(GEN_HEAD(i), GEN_HEAD(generation));
    }

    /* handy references */
    // 在待处理的链表上完成打破循环引用的模拟,寻找root object
    young = GEN_HEAD(generation);
    if (generation < NUM_GENERATIONS-1)
        old = GEN_HEAD(generation+1);
    else
        old = young;

    /* Using ob_refcnt and gc_refs, calculate which objects in the
     * container set are reachable from outside the set (i.e., have a
     * refcount greater than 0 when all the references within the
     * set are taken into account).
     */
    update_refs(young);
    subtract_refs(young);

    /* Leave everything reachable from outside young in young, and move
     * everything else (in young) to unreachable.
     * NOTE:  This used to move the reachable objects into a reachable
     * set instead.  But most things usually turn out to be reachable,
     * so it's more efficient to move the unreachable things.
     */
    // 将待处理的链表中的unreachable object对象,移动到unreachable链表中去
    // 处理完成后当代的链表中就只剩下reachable的对象了
    gc_list_init(&unreachable);
    move_unreachable(young, &unreachable);

    /* 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);
    }

    /* All objects in unreachable are trash, but objects reachable from
     * legacy finalizers (e.g. tp_del) can't safely be deleted.
     */
    // 对于unreachable对象,如果内部定义了__del__函数则不能安全回收
    // 这些对象会被收集到finalizers的链表中,因此这些对象所引用的对象也是不能回收的
    // 也需要放到这个链表中
    gc_list_init(&finalizers);
    move_legacy_finalizers(&unreachable, &finalizers);
    /* finalizers contains the unreachable objects with a legacy finalizer;
     * unreachable objects reachable *from* those are also uncollectable,
     * and we move those into the finalizers list too.
     */
    move_legacy_finalizer_reachable(&finalizers);

    /* Collect statistics on collectable objects found and print
     * debugging information.
     */
    for (gc = unreachable.gc.gc_next; gc != &unreachable;
                    gc = gc->gc.gc_next) {
        m++;
        if (debug & DEBUG_COLLECTABLE) {
            debug_cycle("collectable", FROM_GC(gc));
        }
    }

    /* Clear weakrefs and invoke callbacks as necessary. */
    // 处理弱引用
    m += handle_weakrefs(&unreachable, old);

    /* Call tp_finalize on objects which have one. */
    finalize_garbage(&unreachable);

    if (check_garbage(&unreachable)) {
        revive_garbage(&unreachable);
        gc_list_merge(&unreachable, old);
    }
    else {
        /* Call tp_clear on objects in the unreachable set.  This will cause
         * the reference cycles to be broken.  It may also cause some objects
         * in finalizers to be freed.
         */
         // 对unreachable链表中的对象进行垃圾回收操作
        delete_garbage(&unreachable, old);
    }

    /* Collect statistics on uncollectable objects found and print
     * debugging information. */
    for (gc = finalizers.gc.gc_next;
         gc != &finalizers;
         gc = gc->gc.gc_next) {
        n++;
        if (debug & DEBUG_UNCOLLECTABLE)
            debug_cycle("uncollectable", FROM_GC(gc));
    }
    if (debug & DEBUG_STATS) {
        _PyTime_t t2 = _PyTime_GetMonotonicClock();

        if (m == 0 && n == 0)
            PySys_WriteStderr("gc: done");
        else
            PySys_FormatStderr(
                "gc: done, %zd unreachable, %zd uncollectable",
                n+m, n);
        PySys_WriteStderr(", %.4fs elapsed\n",
                          _PyTime_AsSecondsDouble(t2 - t1));
    }

    /* Append instances in the uncollectable set to a Python
     * reachable list of garbage.  The programmer has to deal with
     * this if they insist on creating this type of structure.
     */
    // 将含有__del__操作的示例对象收集到Python内部维护的叫garbage的链表中
    // 并将finaliziers链表中的对象假如old链表中
    handle_legacy_finalizers(&finalizers, old);

    /* Clear free list only during the collection of the highest
     * generation */
    if (generation == NUM_GENERATIONS-1) {
        clear_freelists();
    }

    if (PyErr_Occurred()) {
        if (nofail) {
            PyErr_Clear();
        }
        else {
            if (gc_str == NULL)
                gc_str = PyUnicode_FromString("garbage collection");
            PyErr_WriteUnraisable(gc_str);
            Py_FatalError("unexpected exception during garbage collection");
        }
    }

    /* Update stats */
    if (n_collected)
        *n_collected = m;
    if (n_uncollectable)
        *n_uncollectable = n;
    stats->collections++;
    stats->collected += m;
    stats->uncollectable += n;

    if (PyDTrace_GC_DONE_ENABLED())
        PyDTrace_GC_DONE(n+m);

    return n+m;
}

  通过上面的代码以及给出注释我想你应该对整个过程全景有了清晰的认识。这里需要说一点Python的垃圾收集机制是为了循环引用设计出来的。对于某些挂在垃圾收集机制监控的链表下的对象,很多时候是引用计数在维护这些对象,只有在出现引用计数无法处理的循环引用上,垃圾收集机制才会起作用。对于循环引用之外的对象,Python的垃圾收集机制是无法起作用的,它们都是被引用计数所维护的。因为在垃圾回收机制监控之下的对象都是引用计数不为0的对象,如果引用计数为0恐怕早就被引用计数给干掉了。而引用计数不为0有两种情况:一是被程序所使用的对象,这类对象是不能回收的;二就是循环引用中的对象,因而垃圾回收机制只能处理循环引用的对象。
  在Python语言层面由一个gc模块,实际上就是cpython中的gcmodule.c的封装,它提供了一些垃圾收集的操作,可参考Python文档了解它的使用。

你可能感兴趣的:(Python源码解析)