Python垃圾回收机制及内存管理

引用计数

python默认的垃圾收集机制是“引用计数”,每个对象维护了一个ob_ref字段。

它的优点是机制简单,当新的引用指向该对象时,引用计数加1, 当一个对象的引用被销毁时减1,一旦对象的引用计数为0,该对象立即被回收,所占有的内存被释放。

它的缺点时需要额外的空间维护引用计数,不过最主要的问题时它不能解决“循环引用”。

什么事循环引用? A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数虽然都为1,但是显然应该被回收,例子:

在这个例子中,del语句减少了a 和 b 的引用计数并删除了用于引用的变量名,可是由于两个对象包含一个对方对象的引用,虽然最后两个对象都无法通过名字访问了,但引用计数并没有减少到零。因此这个对象不会被销毁,它会一直驻留在内存中,这就造成了内存泄漏。

能引发循环引用问题的,都是那种容器类对象,比如list,set,obejct等。对于这类对象,虚拟机在为其分配内存时,会额外添加用于追踪的PyGC_Head。这些对象被添加到特殊链表里,以便GC进行管理

为了解决循环引用问题,python引入了标记-清除分代回收两种GC机制。

 

标记清除

标记-清除(mark–sweep)是一种给予追踪(tracing)回收计数实现的垃圾回收算法。

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的对象标记为有用的对象,不可达的对象就是要被清除的对象。

所谓根对象就是一些全局引用对象和函数栈中的引用,这些引用所引用的对象时不可被删除的。

 

分代回收

分代回收是一种以空间换时间的操作方式,python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,python将内存分为了3 “代”:

分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)

它们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。

新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,python垃圾收集机制就会触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依次类推,老年代中的对象时存活时间最久的对象,甚至时存活与整个系统的生命周期内。

分代回收时建立在标记–清除计数基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象

 

gc模块

如何知道一个对象是否内存泄漏掉了呢?可以通过python的扩张模块gc(garbage collector)来查看不能回收掉的对象的详细信息。

 

python的gc有比较强的功能,比如设置gc.set_debug(gc.DEBUG_LEAK) 就可以进行循环引用导致的内存泄漏的检查。

如果在开发时进行内存泄漏检查;在发布时能够确保不会内存泄漏,那么就可以延长python的垃圾回收时间间隔、甚至主动关闭垃圾回收机制,从而提高运行效率。

 

内存管理

为提升性能,python在内存管理上做了大量工作。最直接的做法就是用内存池来减少操作系统内存分配和回收操作,那些小于等于256字节对象,将直接从内存池中获取存储空间。

根据需要,虚拟机每次从操作系统申请一块256kb,取名为arena的大块内存。并按系统页大小,划分为多个pool。每个pool继续分割成n各大小相同的block,这时内存池最小存储单位。

block大小时8的倍数,也就是说存储13字节大小的对象,需要找block大小为16的pool获取空闲块。所有这些都有头信息和链表管理起来,以便快速查找空闲区域进行分配。

大于256字节的对象,直接用malloc在堆上分配内存。程序运行中的绝大多数都小于这个阈值,因此内存池策略可有效提升性能。

当若有arena的总量超出限制(64MB)时,就不再请求新的arena内存。而是如同“大对象”一样,直接在堆上为对象分配内存。另外,完全空闲的arena会被释放,其内存交还给操作系统。

 

引用传递

todo

 

References:

http://foofish.net/python-gc.html

http://www.cnblogs.com/kaituorensheng/p/4449457.html

《python 学习笔记》 github.com/qyuhen

暂无评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注