python采用计数引用(Reference Counting)为主,来跟踪和回收垃圾。采用标记清除(Mark and Sweep)来解决容器可能产生的循环引用问题、分代回收(Generation Collection)以空间换时间的方法提高垃圾回收效率。
Python中一切皆对象,它们的核心就是一个结构体PyObject
,其中有个obj_refcnt字段,用于记录对象被引用的次数:当一个对象有新的引用时,它的obj_refcount
加1;当对象的引用被删除时,该对象的obj_refcount
减1;当一个对象的obj_refcount
= 0 时,则说明它不再被其他对象引用,就可以回收了。
//object.h
typedef struct_object{
int obj_refcnt;
struct_typeobject *obtype;
} PyObject;
例如下面是int类型的定义
//intobject.h
typedef struct{
PyObject_HEAD
long obj_ival;
} PyIntObject;
obj_refcount
一直不为零,造成 内存泄漏(即内存一直不能释放,造成资源浪费)。所以需要标记-清除机制来解决循环引用问题。
可以包含其他对象的容器对象(list,set,dict,class)都可能产生循环引用问题,因此在申请内存时,所有容器对象头部都加了_GC_Head
来实现“标记-清除“机制。
为解决对象之间循环引用问题,借鉴Ruby的标记清除机制,Python轮询0代对象,将循环引用的对象的引用计数减1。
// objimpl.h
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs;
} gc;
long double dummy; /* force worst-case alignment */
} PyGC_Head;
为对象申请内存的时候,可以明显的看到,实际申请内存的数量已经加上了_GC_Head
的大小。
// gcmodule.c
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();
......
op = FROM_GC(g);
return op;
}
举个栗子,从list
对象的创建中,主要有以下逻辑:
// listobject.c
PyObject *
PyList_New(Py_ssize_t size)
{
PyListObject *op;
......
op = PyObject_GC_New(PyListObject, &PyList_Type);
......
_PyObject_GC_TRACK(op);
return (PyObject *) op;
}
PyObject_TRACK
就将对象链接到了第0代对象集合中。
垃圾回收时,先将对象引用计数复制一份副本(以免在操作过程中破坏真实的引用计数值):
// gcmodule.c
static void
update_refs(PyGC_Head *containers)
{
PyGC_Head *gc = containers->gc.gc_next;
for (; gc != containers; gc = gc->gc.gc_next) {
assert(gc->gc.gc_refs == GC_REACHABLE);
gc->gc.gc_refs = FROM_GC(gc)->ob_refcnt;
assert(gc->gc.gc_refs != 0);
}
}
然后操作这个副本,遍历对象集合,将被引用对象的引用副本减1。
// gcmodule.c
static void
subtract_refs(PyGC_Head *containers)
{
traverseproc traverse;
PyGC_Head *gc = containers->gc.gc_next;
for (; gc != containers; gc=gc->gc.gc_next) {
traverse = FROM_GC(gc)->ob_type->tp_traverse;
(void) traverse(FROM_GC(gc),
(visitproc)visit_decref,
NULL);
}
}
这个traverse
是对象类型定义的函数,用来遍历对象,通过传入的回调函数visit_decref
来操作引用计数副本。
例如dict
就要在key和value上都用visit_decref
操作一遍:
// dictobject.c
static int
dict_traverse(PyObject *op, visitproc visit, void *arg)
{
Py_ssize_t i = 0;
PyObject *pk;
PyObject *pv;
while (PyDict_Next(op, &i, &pk, &pv)) {
visit(pk);
visit(pv);
}
return 0;
}
然后根据引用计数副本值是否为0将集合内的对象分成两类,reachable
和unreachable
,其中unreachable
是可以被回收的对象:
// gcmodule.c
static void
move_unreachable(PyGC_Head *young, PyGC_Head *unreachable)
{
PyGC_Head *gc = young->gc.gc_next;
while (gc != young) {
PyGC_Head *next;
if (gc->gc.gc_refs) {
PyObject *op = FROM_GC(gc);
traverseproc traverse = op->ob_type->tp_traverse;
assert(gc->gc.gc_refs > 0);
gc->gc.gc_refs = GC_REACHABLE;
(void) traverse(op,
(visitproc)visit_reachable,
(void *)young);
next = gc->gc.gc_next;
}
else {
next = gc->gc.gc_next;
gc_list_move(gc, unreachable);
gc->gc.gc_refs = GC_TENTATIVELY_UNREACHABLE;
}
gc = next;
}
}
将系统中所有的内存块根据其存活时间划分为不同的集合,每个集合就成为一“代“,垃圾收集频率随着“代“的存活时间增大而减少,存活时间通常利用经过几次垃圾回收来度量。
也就是说新对象被定义为0代,每次创建新对象时,python会检测0代,满了就开始回收,从0代出发,检测循环引用,释放引用数为0的对象。
为什么是从新对象开始清理呢?
年轻的对象死的快,而越是年老的对象可能存活更长的时间。
——弱代假说
用来表示“代“的结构体是gc_generation
,包括了当前代链表表头、对象数量上限、当前对象数量:
// gcmodule.c
struct gc_generation {
PyGC_Head head;
int threshold; /* collection threshold */
int count; /* count of allocations or collections of younger
generations */
};
Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
#define NUM_GENERATIONS 3
#define GEN_HEAD(n) (&generations[n].head)
/* linked lists of container objects */
static struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
{{{GEN_HEAD(0), GEN_HEAD(0), 0}}, 700, 0},
{{{GEN_HEAD(1), GEN_HEAD(1), 0}}, 10, 0},
{{{GEN_HEAD(2), GEN_HEAD(2), 0}}, 10, 0},
};
新生成的对象会被加入第0代,前面_PyObject_GC_Malloc
中省略的部分就是Python GC触发的时机。每新生成一个对象都会检查第0代有没有满,如果满了就开始着手进行垃圾回收
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;
}
[1]: 简书: http://www.jianshu.com/p/1e375fb40506