由于容器类型的特殊性,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 指针。