09月17, 2020

31. 人生只如初相见,你好,GC!

由于容器类型的特殊性,Python 不得不引入 GC 解决引用计数无法处理 “循环引用” 的问题。也许脑海里还有很多问号,暂时搁置它们,让我们尽快找到 GC,见面才是认识的第一步不是么。我们已经做了相当多的准备,了解内存管理模型从 block 到 pool 最后到 arena,GC 到底在什么时候介入内存管理的呢?

// Modules/gcmodule.c:1951

static PyObject *
_PyObject_GC_Alloc(int use_calloc, size_t basicsize)
{
    // 忽略一些代码

    if (/*很长的判定条件*/) {
        // 标记进入清理状态
        state->collecting = 1;
        // 分代回收
        collect_generations(state);
        // 清除标记
        state->collecting = 0;
    }

    // 从 GC 对象中提取 Object
    op = FROM_GC(g);
    return op;
}

这个 _PyObject_GC_Alloc 就是 PyObject_GC_New 的本尊,经过层层调用,最终会落到上面的函数中。比如 ListObject 创建的时候,实际上调用的就是这个函数。可以看到,在所有的容器创建(分配内存)之时,都会试着进行内存回收,有枣没枣打两杆子! collect_generations 的代码并不复杂:

// Modules/gcmodule.c:1247

static Py_ssize_t
collect_generations(struct _gc_runtime_state *state)
{
    // Python 总共有 3 代内存,从最老一代开始,查看哪一代的对象数量超过阈值,就将这一代及更年轻代内存进行回收
    // 0、1、2 代的阈值分别为 700, 10, 10, 在 29 节中有提到
    Py_ssize_t n = 0;
    for (int i = NUM_GENERATIONS-1; i >= 0; i--) {
        // 分代阈值检测
        if (state->generations[i].count > state->generations[i].threshold) {

            // 省略一些代码
            // 开始内存回收
            n = collect_with_callback(state, i);

            // 结束
            break;
        }
    }
    return n;
}

逻辑非常简单!这里有一个疑问,从最老代内存开始检查,一旦符合阈值要求就开始内存回收,然后就立刻退出了,更年轻代内存在哪里处理的呢?如果继续深入,会发现 GC 的核心函数 collect。

// Modules/gcmodule.c:1006

static Py_ssize_t
collect(struct _gc_runtime_state *state, int generation,
        Py_ssize_t *n_collected, Py_ssize_t *n_uncollectable, int nofail)

在这里可以看到 GC 最核心的逻辑,搞清楚它,基本上就算搞明白 Python GC 以及前面提到的两个 GC 指针。

本文链接:http://www.thinkinpython.com/post/deep_python_vm_31.html

-- EOF --