2009/05/06

Python 中循环引用导致内存泄漏

python的垃圾收集功能。首先python中的所有对象都是继承object对象,python源码中关于object类型的定义:
[object.h]
typedef struct _object{
int ob_refcnt;
struct _typeobject * ob_type;
} PyObject;
其中ob_type是用来确定对象类型的变量,而ob_refcnt是用于垃圾收集的对象的引用计数。当对象创建的时候此成员的值就是1,例如 a=MyType(),a的引用计数就是1,如果进行对象赋值操作,例如b = a, a的引用计数就变成2。相对的,如果再运行b = c,那么a的引用计数就减1. 当对象的引用计数为0时,表明没有其他对象引用该对象。该对象所分配的内存就可以回收了

当一个对象的引用计数减少至零时,它就会在适当时机被垃圾回收车拉走。然而,特定情况(循环引用)会阻止垃圾回收车销毁不再使用的对象,看下面的例子:

1 a = { } # a 的引用为 1
2 b = { } # b 的引用为 1
3 a['b'] = b # b 的引用增 1,b的引用为2
4 b['a'] = a # a 的引用增 1,a的引用为 2
5 del a # a 的引用减 1,a的引用为 1
6 del b # b 的引用减 1, b的引用为 1

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

解决办法1: break the circle.
before 5: a['b'] = None

因此,有 __del__() 函数的对象(如list,dict)间的循环引用是导致内存泄漏的主凶。特别说明:对没有 __del__() 函数的 Python 对象(如int)间的循环引用,是可以被自动垃圾回收掉的。

慎写自己的__del__() 函数啊 :-(

解决办法2:
定期的运行 pythn gc module的搜索器,若发现一个对象已经无法被访问 (untouchable),不论该对象引用计数是否为 0 ,都主动销毁它 del gc.garbage[:]。通过 gc 模块的函数来进行调整和控制gc.enable()

print 'begin collect...'
_unreachable = gc.collect()
print 'unreachable object num:%d' % _unreachable
print 'garbage object num:%d' % len(gc.garbage)
del gc.garbage[:]

解决办法3: (调试)
# 通过引用计数判断数据单元是否可以回收
# 通过扩展模块gc中的接口可以分析调试垃圾回收的情况

使用get_objects( )方法可以取得当前所有不能回收的对象(引用计数不为0)的列表
可以写个循环把他们都打印出来调试。该方法只执行一次,因为 它本生会增加变量的引用,特别是在有内存泄露的情况下.
For example:

# after potential wrong code
objs = gc.get_objects()
for obj in objs:
if isinstance( obj, XXX): print obj # check objects here





References:
[1] Python的Garbage Collector module (import gc)
http://docs.python.org/library/gc.html

[2] http://blog.csdn.net/horin153/archive/2007/06/08/1644512.aspx

没有评论: