Python 内存管理架构
Object-specific allocators
_____ ______ ______ ________
[int] [ dict ] [ list ] ... [ string ] Python core |
+3 | <----- Object-specific memory -----> | <-- Non-object memory --> |
_______________________________ | |
[Python's object allocator] | |
+2 | ####### Object memory ####### | <------ Internal buffers ------> |
______________________________________________________________ |
[Python's raw memory allocator (PyMem_ API) ] |
+1 | <----- Python memory (under PyMem manager's control) ------> | |
__________________________________________________________________
[Underlying general-purpose allocator (ex: C library malloc) ]
0 | <------ Virtual memory allocated for the python process -------> |
=========================================================================
_______________________________________________________________________
[OS-specific Virtual Memory Manager (VMM) ]
-1 | <--- Kernel dynamic storage allocation & management (page-based) ---> |
__________________________________ __________________________________
[] []
-2 | <-- Physical memory: ROM/RAM --> | | <-- Secondary storage (swap) --> |
Python 层面的内存管理重点在 +2 层,其代码在 obmalloc.c,主要的 API 是 PyObject_Malloc 和 PyObject_Free。
小内存的阈值是 SMALL_REQUEST_THRESHOLD = 256, 如果欲分配的内存小于该阈值,则使用小内存分配技术,否则退到第+1层的 PyMem_xxx API。
小内存管理涉及到一下几个概念:
-
arena
-
pool
-
block
具体细节查看PyObject_Malloc 和 PyObject_Free的源码及原文。
引用计数原理简单,实现起来也比较简单。
优点:
-
原理简单,容易实现。
-
垃圾回收的实时性高,一旦对象的引用技术变为0,马上可以回收其占用的内存。
缺点:
-
循环引用
-
维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的。
循环引用只会发生在 container 对象中,例如 list,dict 等。
循环引用例子:
In [505]: l1 = []
In [506]: l2 = []
In [507]: l1.append(l2)
In [508]: l2.append(l1)
In [509]: print l1
[[[...]]]
In [510]: print l2
[[[...]]]
In [511]: +----------+ +----------+
+--> list 1 | +-------> list 1 |
| +----------+ | +----------+
| | +---+ +--+ |
| +----------+ | +----------+
| | | | | |
| +----------+ | +----------+
| | | | | |
| +----------+ | +----------+
| |
+----------------------+
为了解决循环引用问题,引入了标记-清除垃圾回收机制和分代垃圾回收机制。
简要工作过程如下:
-
寻找根对象(root object)的集合,所谓的root object即是一些全局引用和函数栈中的引用。这些引用所用的对象是不可被删除的。而这个root object集合也是垃圾检测动作的起点
-
从root object集合出发,沿着root object集合中的每一个引用,如果能到达某个对象A,则A称为可达的(reachable),可达的对象也不可被删除。这个阶段就是垃圾检测阶段
-
当垃圾检测阶段结束后,所有的对象分为了可达的和不可达的(unreachable)两部分,所有的可达对象都必须予以保留,而所有的不可达对象所占用的内存将被回收,这就是垃圾回收阶段
在垃圾收集动作被激活之前,系统中所分配的所有对象和对象之间的引用组成了一张有向图,其中对象是图中的节点,而对象间的引用是图的边。
由于只有 contaier 对象才会存在循环引用的问题,所以 Python 为这些特定对象增加了一个额外的头信息,即PyGG_Head。
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
int gc_refs;
} gc;
long double dummy; /* force worst-case alignment */
} PyGC_Head;PyObject *
_PyObject_GC_Malloc(size_t basicsize)
{
PyObject *op;
PyGC_Head *g = (PyGC_Head *)PyObject_MALLOC(
sizeof(PyGC_Head) + basicsize);
if (g == NULL)
return PyErr_NoMemory();
g->gc.gc_refs = GC_UNTRACKED;
generations[0].count++; /* number of allocated GC objects */
if (generations[0].count > generations[0].threshold &&
enabled &&
generations[0].threshold &&
!collecting &&
!PyErr_Occurred()) {
collecting = 1;
collect_generations();
collecting = 0;
}
op = FROM_GC(g);
return op;
}通过 PyGC_Head,所有的 container 对象形成一个双向链表。
分代的垃圾回收机制的基本原理是:将所有的对象(内存块)按照其存活时间划分成不同的集合(代),对象的存活时间越长,说明该对象越不可能是垃圾,被回收的概率越低。