X Tutup
Skip to content

Latest commit

 

History

History
156 lines (112 loc) · 5.14 KB

File metadata and controls

156 lines (112 loc) · 5.14 KB

Python 的内存管理机制

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_MallocPyObject_Free

小内存池

小内存的阈值是 SMALL_REQUEST_THRESHOLD = 256, 如果欲分配的内存小于该阈值,则使用小内存分配技术,否则退到第+1层的 PyMem_xxx API。

小内存管理涉及到一下几个概念:

  • arena

  • pool

  • block

具体细节查看PyObject_MallocPyObject_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  |
|  +----------+   |       +----------+
|  |          +---+    +--+          |
|  +----------+        |  +----------+
|  |          |        |  |          |
|  +----------+        |  +----------+
|  |          |        |  |          |
|  +----------+        |  +----------+
|                      |
+----------------------+

为了解决循环引用问题,引入了标记-清除垃圾回收机制和分代垃圾回收机制。

标记-清除(mark-sweep)

简要工作过程如下:

  • 寻找根对象(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 对象形成一个双向链表。

分代的垃圾回收机制

分代的垃圾回收机制的基本原理是:将所有的对象(内存块)按照其存活时间划分成不同的集合(代),对象的存活时间越长,说明该对象越不可能是垃圾,被回收的概率越低。

X Tutup