在上面中,我们手动删除了 a 和 b ,理应进行释放,但由于 a 和 b 互相构成了循环引用,导致其引用计数总是不为0,进而造成内存泄漏,而 CPython 对其解决方法也极其简单,就是将所有可能造成循环引用的对象,构成一个双向链表进行扫描,从 root object 出发进行扫描 - 清除,无法到达的对象就是可释放的对象,普通的对象直接采用引用计数去释放,简单快捷。
怎么去验证以上结论呢?我们可以用反证法,当 del a 和 del b 后,再调用 gc.collect() 查看其是否能被回收到,如果能回收到,说明在此时引用计数已经失效。
[<__main__.A object at 0x10adefc10>, <__main__.B object at 0x10adeff70>, {'b': <__main__.B object at 0x10adeff70>}, {'a': <__main__.A object at 0x10adefc10>}]
/* GC information is stored BEFORE the object structure. */ typedefstruct { // Pointer to next object in the list. // 0 means the object is not tracked uintptr_t _gc_next;
// Pointer to previous object in the list. // Lowest two bits are used for flags documented later. uintptr_t _gc_prev; } PyGC_Head;
// Lowest bit of _gc_next is used for flags only in GC. // But it is always 0 for normal code. #define _PyGCHead_NEXT(g) ((PyGC_Head*)(g)->_gc_next) #define _PyGCHead_SET_NEXT(g, p) _Py_RVALUE((g)->_gc_next = (uintptr_t)(p))
// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags. #define _PyGCHead_PREV(g) ((PyGC_Head*)((g)->_gc_prev & _PyGC_PREV_MASK)) #define _PyGCHead_SET_PREV(g, p) do { \ assert(((uintptr_t)p & ~_PyGC_PREV_MASK) == 0); \ (g)->_gc_prev = ((g)->_gc_prev & ~_PyGC_PREV_MASK) \ | ((uintptr_t)(p)); \ } while (0)
static Py_ssize_t gc_collect_main(PyThreadState *tstate, 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 */ GCState *gcstate = &tstate->interp->gc;
// 将更老的一代的 count + 1 从而让之后能执行到后续的垃圾回收 if (generation+1 < NUM_GENERATIONS) gcstate->generations[generation+1].count += 1; // 当前代和比当前代更年轻的计数重置,因为我们会将[0, 当前代]全部处理完 for (i = 0; i <= generation; i++) gcstate->generations[i].count = 0;
// 将更年轻的代归到当前代的链表上 for (i = 0; i < generation; i++) { gc_list_merge(GEN_HEAD(gcstate, i), GEN_HEAD(gcstate, generation)); }
// young = [0, 当前代] young = GEN_HEAD(gcstate, generation); if (generation < NUM_GENERATIONS-1) // 当当前为第1代则old为第2代,当当前为第0代则old为第1 old = GEN_HEAD(gcstate, generation+1); else // 说明当前为第2代,则old也为第2代 old = young;
CPython 的GC是 Stop The World 的,哪怕它已经很尽力用分代的方式去减少GC的损耗。是否可以将其改进为渐进的方式?我目前的想法是在容器操作时,进行 Barrier 操作,维护一个中间态,使得前面的扫描过程是可渐进的,最后处理垃圾的时候再停下来一次性处理完,减少停止的时间。但这个思路貌似不行,原因是根集是不确定的。