Python的内存管理机制采用“引用计数”
引用计数最大的优点就是“实时性”,任何内存,一旦没有了指向它的引用,就会被立即收回。
引用计数机制所带来的维护引用计数的额外操作会降低Python的执行效率,但是引用计数更为致命的弱点是“循环引用”。
计数引用的机制非常简单,当一个对象的引用被创建或复制时,对象的引用计数+1,当一个对象的引用被销毁时,对象的引用计数-1。如果对象的引用计数减少为0,那么意味着对象已经不会被任何人使用,可以将其占用的内存释放。但那循环引用可以使一组对象的引用计数都不为0,它们之间只是相互引用,正是由于相互引用,导致了他们的引用计数都不为0,因此这些对象的内存都不会被回收,这一点和手动进行内存管理所产生的内存泄漏毫无区别。为了解决这个问题,必须引入其他的垃圾收集技术来打破循环引用。
Python引入了主流的垃圾收集技术中的“标记-清除”和“分代收集”两种技术来打破循环引用。
标记-清除
无论何种垃圾收集机制,一般都分为两个阶段,垃圾检测和垃圾回收。
垃圾检测是从已分配的内存中区别出 可回收的内存 和 不可回收的内存,而垃圾回收则是使系统重新掌握在垃圾检测阶段所标出的可回收内存块。
“标记-清除”方法同样遵循垃圾收集机制的两个阶段,其简要工作过程如下:寻找根对象(root object)的集合,root object指的是一些全局引用和函数栈中的引用,这些引用所用的对象是不可被删除的,而这个root object也是垃圾检测动作的起点。
从root object出发,沿着root object集合中的每一个引用,如果能到达某个对象A,则A称为可达的(reachable),可达的对象不被删除,这个阶段就是垃圾检测阶段
当垃圾检测阶段结束后,所有的对象分为了可达的和不可达的部分,所有可达对戏那个都必须保留,而不可达的对象占用的内存则会被回收,这就是垃圾回收机制。
垃圾收集
在Python中,主要的内存管理手段是引用计数机制,“标记-清除”和“分代收集”是为了打破循环引用而引用的补充技术。所以Python中的垃圾收集只会关注 产生循环引用的对象,而Python中的循环引用总是发生在 container 对象之间,比如list、dict、class等,所以垃圾收集带来的开销只依赖于container对象的数量。
可收集对象链表
Python采用了一个双向链表,所有的container对象在创建之后,都会被插入到这个链表中。
任何一个Python对象都分为两部分,一部分是PyObject_HEAD,一部分是对象自身的数据,然后,一个 container 想成为一个可收集的对象,则必须加入其他信息,这个信息位于PyObject_HEAD之前,称为PyGC_HEAD。
/* GC information is stored BEFORE the object structure. */
typedef union _gc_head {
struct {
union _gc_head *gc_next;
union _gc_head *gc_prev;
Py_ssize_t gc_refs;
} gc;
double dummy; /* Force at least 8-byte alignment. */
char dummy_padding[sizeof(union _gc_head_old)];
} PyGC_Head;
当Python为container申请内存时,也为PyGC_HEAD申请了内存空间,且位置在container对象之前。
并且在创建container的最后一步, Python会将所创件的container对象链接到Python可收集对象链表中。Python会将垃圾收集机制限制在可收集对象链表上,因为所有的循环引用一定是发生在这个链表中的一群对象之间。
分代的垃圾收集
图
在Python中,共有三个“代”,一个“代”就是一个链表,在_PyObject_GC_TRACK 中 _PyGC_generation0是Python内部维护的指针,指向的正是Python中第0代的内存块集合,上文的可收集对象链表也是一个“代”。为了支持分代机制,所需要的仅仅是一个表头。
/*** Global GC state ***/
struct {
PyGC_Head head;
int threshold; /* collection threshold */
int count; /* count of allocations or collections of younger
generations */
};
#define GEN_HEAD(n) (&generations[n].head)
/* linked lists of container objects */
static struct 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},
};
PyGC_Head *_PyGC_generation0 = GEN_HEAD(0);
threshold记录该条可收集对象链表中最多可容纳多少可收集对象。第0代最多可容纳700个container对象,一旦第0代内存链表的count超过700,便会立即触发垃圾回收机制。虽然是第0代内存链表触发了垃圾回收,但是Python借此机会对所有的“代”都进行了垃圾收集。