对于编程语言runtime来说,建立起良好运转GC机制是非常必要的,像Java
和Go
,其GC机制都经历了复杂的演化,当然同时也为编程语言带来了更好的性能,这也是为什么这两门语言能成为主流服务端语言的原因之一。
相对于Java
和Go
,python
的GC机制是相对简约的,其中最基础的机制之一就是引用计数。当对象生成时引用计数为1;对象被其它对象引用时引用计数增加1;对象没有被引用,又退出作用域的话,引用计数归0;引用计数归0后,对象被销毁。
我们可以通过一个例子对引用计数机制进行研究:
1 | def test_ref(): |
其反编译的结果是:
1 | 8 0 LOAD_CONST 1 ('123456789123456789') |
在STORE_FAST
和DELETE_FAST
操作中,都用到了SET_LOCAL
宏:
1 |
|
可以看到SET_LOCAL
的操作是:将对应LOCAL
位置的旧值拷贝到tmp
指针,让后将新值赋给对应LOCAL
位置,最后减少旧值tmp
指针的引用计数
对于DELETE_FAST
操作,SETLOCAL
的value
参数是NULL
,这样对应LOCAL
位置指针被赋值为NULL
,旧值减少引用计数,这样就触发后续一系列操作了。
当调用Py_XDECREF
时,实际执行了如下的操作:
1 | static inline void _Py_XDECREF(PyObject *op) |
可以看到,如果对象内部的ob_refcnt
引用计数归0,就会触发_Py_Dealloc
逻辑,清空给对象分配的内存。
针对test_ref
的例子,会调用py3的字符串析构逻辑,也就是unicode
的dealloc
逻辑:
1 | static void |
在unicode
的析构逻辑中,首先判断字符串是不是intern
的(短字符串缓存),如果是的话会启用另外的析构逻辑,否则会跑到下面。最终调用的是Py_TYPE(unicode)->tp_free(unicode)
逻辑。
1 | void |
一路走下来,最终会调用_PyObject_Free
去彻底释放这块内存,_PyObject_Free
会尝试通过pymalloc_free
和PyMem_RawFree
两种方式对对象所占内存进行释放。其中前者是采用python
自带的内存管理机制,后者是采用操作系统的free
方法。
这里我们需要稍微了解一下python
内存管理的机制。python
内部维护了不同组相同大小块的内存池,其中有几个概念:
arena
:管理一组pool
- 维护可用
pool
数量及pool
总量 - 可以有多个
arena
- 维护可用
pool
:管理一组相同大小的block
的链表- 正在使用中(
used
,非空,非满)的pool
集合,会单独由usedpools
数组管理 - 申请内存时,会优先从
usedpools
寻找可用pool
。如果block
数量不够,会新增block
- 如果没有指定大小的
block
,会从arena
新起一个pool
然后分配对应block
- 释放对象内存时,被释放的
block
被转移到单独的可用block
链表
- 正在使用中(
block
:一个固定大小的内存块- 在
python
中,有不同的固定大小,以8/16字节对齐(ALIGNMENT
宏) - 针对不同大小的对象,分配不同大小的
block
- 在
了解了这些概念,再看python
对象的内存回收逻辑,就很明白了:
1 | static inline int |
最终我们可以看到,pymalloc_free
主要做了以下几件事情,完成对象的内存释放:
- 通过
POOL_ADDR
宏,找到指针p
对应pool
位置 - 将指针
p
对应的内存块放到freeblock
链表头部 - 减少
pool
的引用数。如果归零,将pool
放到对应arena
的freepools
里