Python内存管理:垃圾回收
http://blog.csdn.net/pipisorry/article/details/39647931Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收
http://blog.csdn.net/pipisorry/article/details/39647931
Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。也就是Python中的垃圾回收是以引用计数为主,分代收集为辅。
引用计数
概述
引用计数法在对象内部维护了一个被其他对象引用数的引用计数值,当这个引用计数值为0时,说明这个对象不再被其他对象引用,就可以被回收了。
结合源码来看,所有Python对象的头部包含了这样一个结构PyObject(相当于继承自PyObject):
// object.h struct _object { Py_ssize_t ob_refcnt; struct PyTypeObject *ob_type; } PyObject;
ob_refcnt就是引用计数值。
例如,下面是int型对象的定义:
// intobject.h typedef struct { PyObject_HEAD long ob_ival; } PyIntObject;
引用计数的增减
导致引用计数+1的情况
对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]
导致引用计数-1的情况
对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象
引用计数示例
def func(c):
print 'in func function', sys.getrefcount(c) - 1
print 'init', sys.getrefcount(11) - 1
a = 11
print 'after a=11', sys.getrefcount(11) - 1
b = a
print 'after b=1', sys.getrefcount(11) - 1
func(11)
print 'after func(a)', sys.getrefcount(11) - 1
list1 = [a, 12, 14]
print 'after list1=[a,12,14]', sys.getrefcount(11) - 1
a=12
print 'after a=12', sys.getrefcount(11) - 1
del a
print 'after del a', sys.getrefcount(11) - 1
del b
print 'after del b', sys.getrefcount(11) - 1
# list1.pop(0)
# print 'after pop list1',sys.getrefcount(11)-1
del list1
print 'after del list1', sys.getrefcount(11) - 1
init 24
after a=11 25
after b=1 26
in func function 28
after func(a) 26
after list1=[a,12,14] 27
after a=12 26
after del a 26
after del b 25
after del list1 24
查看一个对象的引用计数:sys.getrefcount(a)
可以查看a对象的引用计数,但是比正常计数大1,因为调用函数getrefcount的时候传入a,这也会让a的引用计数+1。
循环引用导致内存泄露
def f2():
while True:
c1=ClassA()
c2=ClassA()
c1.t=c2
c2.t=c1
del c1
del c2
执行f2(),进程占用的内存会不断增大。
创建了c1,c2后,0x237cf30
(c1对应的内存,记为内存1),
0x237cf58
(c2对应的内存,记为内存2)这两块内存的引用计数都是1,执行
c1.t=c2
和
c2.t=c1
后,这两块内存的引用计数变成2。在del c1后,内存1的对象的引用计数变为1,由于不是为0,所以内存1的对象不会被销毁,所以内存2的对象的引用数依然是2,在del c2后,同理,内存1的对象,内存2的对象的引用数都是1。
虽然它们两个的对象都是可以被销毁的,但是 由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。
引用计数法优缺点
引用计数法有很明显的优点
高效
运行期没有停顿
对象有确定的生命周期
易于实现
原始的引用计数法也有明显的缺点:
维护引用计数的次数和引用赋值成正比,而不像mark and sweep等基本与回收的内存数量有关。
无法解决循环引用的问题。A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数都为1,但显然应该被回收。
为了解决这两个致命弱点,Python又引入了以下两种GC机制。
标记-清除
“标记-清除”法是为了解决循环引用问题。可以包含其他对象引用的容器对象(如list, dict, set,甚至class)都可能产生循环引用,为此,在申请内存时,所有容器对象的头部又加上了PyGC_Head
来实现“标记-清除”机制。
// 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;垃圾标记时,先将集合中对象的引用计数复制一份副本(以免在操作过程中破坏真实的引用计数值)。然后操作这个副本,遍历对象集合,将被引用对象的引用计数副本值减1。然后根据引用计数副本值是否为0将集合内的对象分成两类,reachable和unreachable,其中unreachable是可以被回收的对象。在处理了weak reference和finalizer等琐碎细节后(本文不展开讲述,有兴趣的请参考python源码),就可以回收unreachable中的对象了。
分代回收
分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
用来表示“代”的结构体是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默认定义了三代对象集合,索引数越大,对象存活时间越长。新生成的对象会被加入第0代,前面_PyObject_GC_Malloc
中省略的部分就是Python GC触发的时机。每新生成一个对象都会检查第0代有没有满,如果满了就开始着手进行垃圾回收。
gc模块常用功能解析
Garbage Collector interface
gc模块提供一个接口给开发者设置垃圾回收的选项。上面说到,采用引用计数的方法管理内存的一个缺陷是循环引用,而gc模块的一个主要功能就是解决循环引用的问题。
应用
项目中避免循环引用
引入gc模块,启动gc模块的自动清理循环引用的对象机制
由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到二代以后,减少GC检查时的消耗
gc模块唯一处理不了的是循环引用的类都有__del__方法,所以项目中要避免定义__del__方法,如果一定要使用该方法,同时导致了循环引用,需要代码显式调用gc.garbage里面的对象的__del__来打破僵局
from:http://blog.csdn.net/pipisorry/article/details/39647931
ref: [python的内存管理机制]
[《Python源码剖析》,陈儒著,2008]
[Python垃圾回收机制]*
更多推荐
所有评论(0)