Python GC

python的垃圾收集是引用计数的补充,所以它的工作原理和教科书上的mark-sweep有着不同——它会用到引用计数的值;进入垃圾收集的对象都是容器(可包含 PyObject 的对象,它们必须提供 tp_traverse 函数实现);它没有直接的 root object,(传统的流程是直接将 root object 放入unscaned 队列,然后一个收集周期就开始了),经过 subtract_refs(young) 后才找到 root object

 

python 垃圾分为三代(非增量)

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

threshold 对 0 代的含义为 700 个对象分配;对 1,2 代对象的含义为 10 次上代对象的垃圾收集

 

 

查看 gcmodule.c 的 collect 函数即可一窥 python 收集机制的全貌,如下:

 

    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__ */

以上都是双向循环链表

 

    /* merge younger generations with one we are currently collecting */
    for (i = 0; i < generation; i++) {
        gc_list_merge(GEN_HEAD(i), GEN_HEAD(generation));
    }

将更年轻的代,合并到需要收集的代里面

 

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


将GC头的gc_refs设置为引用计数的值

然后将遍历链表,对每个对象调用 traverse 遍历对象所包含的子对象,将对象所包含的子对象的gc_refs值减1


结果:

对象 gc_refs == 0 表示此对象仅被本集合(需要收集的代以及更年轻的代)的对象包含(注意:并不表示该对象为垃圾,可以直接删除)

对象 gc_refs > 0 表示此对象被集合外的对象所包含

 

    /* 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.
     */
    gc_list_init(&unreachable);
    move_unreachable(young, &unreachable);

将不可达对象移入unreachable。

实现是这样的,从young开始往后走,到young为止(前面提到过的,young是循环链表):

1)将 gc_refs == 0 的对象移入 unreachable;

2)对 gc_refs > 0 的对象调用 traverse 遍历对象所包含的子对象,如果子对象在unreachable中,则将该对象插入 young 的前面并将该对象 gc_refs=1


如果觉得上面一个循环干的事太杂,想不明白。则可以这么理解:首先,gc_refs == 0 的对象移入 unreachable,young则只剩下 gc_refs > 0的对象;接着,就是传统mark-sweep的作法了, young 为 root object……

 

    /* 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.
     */
    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 {
        long_lived_pending = 0;
        long_lived_total = gc_list_size(young);
    }

合并前面步骤找到的可达对象至更老的一代(如果存在更老的代)

 

    /* 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.
     */
    delete_garbage(&unreachable, old);

释放对象(实现里面有个小技巧,先自增容器的引用计数,清空容器——会对包含的子对象们减引用计数,接着再减容器的引用计数)

posted on 2012-11-16 16:32  JesseFang  阅读(1309)  评论(0编辑  收藏  举报