Python的内存泄漏及gc模块的使用

作者: 空气 分类: Python, 转载收藏 发布时间: 2013-02-16 19:33 ė1479 6没有评论

原文作者: Horin|贺勤
Email: horin153@msn.com
Blog: http://blog.csdn.net/horin153/

在 Python 中,为了解决内存泄漏问题,采用了对象引用计数,并基于引用计数实现自动垃圾回收。
因为 Python 有了自动垃圾回收功能,不少初学者就认为自己从此过上了好日子,不必再受内存泄漏的骚扰了。但如果查看一下 Python 文档对 __del__() 函数的描述,就知道好日子里也是有阴云的。下面摘抄一点文档内容:
 

Some common situations that may prevent the reference count of an object from going to zero include: circular references between objects (e.g., a doubly-linked list or a tree data structure with parent and child pointers); a reference to the object on the stack frame of a function that caught an exception (the traceback stored in sys.exc_traceback keeps the stack frame alive); or a reference to the object on the stack frame that raised an unhandled exception in interactive mode (the traceback stored in sys.last_traceback keeps the stack frame alive).
 
可见,有 __del__() 函数的对象间的循环引用是导致内存泄漏的主凶。特别说明:对没有 __del__() 函数的 Python 对象间的循环引用,是可以被自动垃圾回收掉的。
 
如何知道一个对象是否内存泄漏了呢?
方法一、当你认为一个对象应该被销毁时(即引用计数为 0),可以通过 sys.getrefcount(obj) 来获取对象的引用计数,并根据返回值是否为 0 来判断是否内存泄漏。如果返回的引用计数不为 0,说明在此刻对象 obj 是不能被垃圾回收器回收掉的。
 
方法二、也可以通过 Python 扩展模块 gc 来查看不能回收的对象的详细信息。
 

首先,来看一段正常的测试代码:

 

在 test_gcleak() 中,设置垃圾回收器调试标志后,再用 collect() 进行垃圾回收,最后打印出该次垃圾回收发现的不可达的垃圾对象数和整个解释器中的垃圾对象数。

 
gc.garbage 是一个 list 对象,列表项是垃圾收集器发现的不可达(即是垃圾对象)、但又不能释放(即不能回收)的对象。文档描述为:A list of objects which the collector found to be unreachable but could not be freed (uncollectable objects).
通常,gc.garbage 中的对象是引用环中的对象。因为 Python 不知道按照什么样的安全次序来调用环中对象的 __del__() 函数,导致对象始终存活在 gc.garbage 中,造成内存泄漏。如果知道一个安全的次序,那么就打破引用环,再执行 del gc.garbage[:] ,以清空垃圾对象列表。
 
上段代码输出为(#后字符串为笔者所加注释):

可见 _gcleak 对象的引用计数是正确的,也没有任何对象发生内存泄漏。
 
如果不注释掉 make_circle_ref() 中的 test_code_1 语句:

也就是让 _gcleak 形成一个自己对自己的循环引用。再运行上述代码,输出结果就变成:

 
可见 <CGcLeak 012AA090> 对象发生了内存泄漏!!而多出的 dict 垃圾就是泄漏的 _gcleak 对象的字典,打印出字典信息为:

 

除了对自己的循环引用,多个对象间的循环引用也会导致内存泄漏。简单举例如下:

 

这次测试后输出结果为:

可见 _a,_b 对象都发生了内存泄漏。因为二者是循环引用,垃圾回收器不知道该如何回收,也就是不知道该首先调用那个对象的 __del__() 函数。
 
采用以下任一方法,打破环状引用,就可以避免内存泄漏:
[1] 注释掉 make_circle_ref() 中的 test_code_2 语句;
[2] 注释掉 make_circle_ref() 中的 test_code_3 语句;
[3] 取消对 make_circle_ref() 中的 test_code_4 语句的注释。
 
相应输出结果变为:

 
 
结论:Python 的 gc 有比较强的功能,比如设置 gc.set_debug(gc.DEBUG_LEAK) 就可以进行循环引用导致的内存泄露的检查。如果在开发时进行内存泄露检查;在发布时能够确保不会内存泄露,那么就可以延长 Python 的垃圾回收时间间隔、甚至主动关闭垃圾回收机制,从而提高运行效率。
 
 
推荐阅读文献
 
1, Python垃圾回收算法描述: http://wiki.woodpecker.org.cn/moin/python_ref_circle_gc
2, Garbage Collection for Python: http://arctrix.com/nas/python/gc/

分享此文到:

本文出自 空气的时光记事本,非注明转载皆为原创,转载时请注明出处及相应链接。

本文永久链接: http://www.liujingze.com/python%e7%9a%84%e5%86%85%e5%ad%98%e6%b3%84%e6%bc%8f%e5%8f%8agc%e6%a8%a1%e5%9d%97%e7%9a%84%e4%bd%bf%e7%94%a8.html

发表评论

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

*