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