一般来说在 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 来查看不能回收的对象的详细信息。

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

#--------------- code begin --------------
# -*- coding: utf-8 -*-
import gc
import sys class CGcLeak(object):
def __init__(self):
self._text = '#'*10 def __del__(self):
pass def make_circle_ref():
_gcleak = CGcLeak()
# _gcleak._self = _gcleak # test_code_1
print '_gcleak ref count0:%d' % sys.getrefcount(_gcleak)
del _gcleak
try:
print '_gcleak ref count1:%d' % sys.getrefcount(_gcleak)
except UnboundLocalError:
print '_gcleak is invalid!' def test_gcleak():
# Enable automatic garbage collection.
gc.enable()
# Set the garbage collection debugging flags.
gc.set_debug(gc.DEBUG_COLLECTABLE | gc.DEBUG_UNCOLLECTABLE | /
gc.DEBUG_INSTANCES | gc.DEBUG_OBJECTS) print 'begin leak test...'
make_circle_ref() print 'begin collect...'
_unreachable = gc.collect()
print 'unreachable object num:%d' % _unreachable
print 'garbage object num:%d' % len(gc.garbage) if __name__ == '__main__':
test_gcleak()

在 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[:] ,以清空垃圾对象列表。

上段代码输出为(#后字符串为笔者所加注释):

#-----------------------------------------
begin leak test...
# 变量 _gcleak 的引用计数为 2.
_gcleak ref count0:2
# _gcleak 变为不可达(unreachable)的非法变量.
_gcleak is invalid!
# 开始垃圾回收
begin collect...
# 本次垃圾回收发现的不可达的垃圾对象数为 0.
unreachable object num:0
# 整个解释器中的垃圾对象数为 0.
garbage object num:0
#-----------------------------------------

由此可见 _gcleak 对象的引用计数是正确的,也没有任何对象发生内存泄漏。

如果不注释掉 make_circle_ref() 中的 test_code_1 语句:

_gcleak._self = _gcleak

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

#-----------------------------------------
begin leak test...
_gcleak ref count0:3
_gcleak is invalid!
begin collect...
# 发现可以回收的垃圾对象: 地址为 012AA090,类型为 CGcLeak.
gc: uncollectable <CGcLeak 012AA090>
gc: uncollectable <dict 012AC1E0>
unreachable object num:2
#!! 不能回收的垃圾对象数为 1,导致内存泄漏!
garbage object num:1
#-----------------------------------------

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

{'_self': <__main__.CGcLeak object at 0x012AA090>, '_text': '##########'}

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

#--------------- code begin --------------

class CGcLeakA(object):
def __init__(self):
self._text = '#'*10 def __del__(self):
pass class CGcLeakB(object):
def __init__(self):
self._text = '*'*10 def __del__(self):
pass def make_circle_ref():
_a = CGcLeakA()
_b = CGcLeakB()
_a._b = _b # test_code_2
_b._a = _a # test_code_3
print 'ref count0:a=%d b=%d' % /
(sys.getrefcount(_a), sys.getrefcount(_b))
# _b._a = None # test_code_4
del _a
del _b
try:
print 'ref count1:a=%d' % sys.getrefcount(_a)
except UnboundLocalError:
print '_a is invalid!'
try:
print 'ref count2:b=%d' % sys.getrefcount(_b)
except UnboundLocalError:
print '_b is invalid!' #--------------- code end ----------------

这次测试后输出结果为:

#-----------------------------------------
begin leak test...
ref count0:a=3 b=3
_a is invalid!
_b is invalid!
begin collect...
gc: uncollectable <CGcLeakA 012AA110>
gc: uncollectable <CGcLeakB 012AA0B0>
gc: uncollectable <dict 012AC1E0>
gc: uncollectable <dict 012AC0C0>
unreachable object num:4
garbage object num:2
#-----------------------------------------

可见 _a,_b 对象都发生了内存泄漏。因为二者是循环引用,垃圾回收器不知道该如何回收,也就是不知道该首先调用那个对象的 __del__() 函数。

采用以下任一方法,打破环状引用,就可以避免内存泄漏:

1.注释掉 make_circle_ref() 中的 test_code_2 语句;
2.注释掉 make_circle_ref() 中的 test_code_3 语句;
3.取消对 make_circle_ref() 中的 test_code_4 语句的注释。

相应输出结果变为:

#-----------------------------------------
begin leak test...
ref count0:a=2 b=3 # 注:此处输出结果视情况变化.
_a is invalid!
_b is invalid!
begin collect...
unreachable object num:0
garbage object num:0
#-----------------------------------------

结论:Python 的 gc 有比较强的功能,比如设置 gc.set_debug(gc.DEBUG_LEAK) 就可以进行循环引用导致的内存泄露的检查。如果在开发时进行内存泄露检查;在发布时能够确保不会内存泄露,那么就可以延长 Python 的垃圾回收时间间隔、甚至主动关闭垃圾回收机制,从而提高运行效率。

<转>Python的内存泄漏及gc模块的使用分析的更多相关文章

  1. Python之内存泄漏和内存溢出

    预习知识:python之MRO和垃圾回收机制 一.内存泄漏 像Java程序一样,虽然Python本身也有垃圾回收的功能,但是同样也会产生内存泄漏的问题.对于一个用 python 实现的,长期运行的后台 ...

  2. python标准库介绍——14 gc 模块详解

    ==gc 模块== (可选, 2.0 及以后版本) ``gc`` 模块提供了到内建循环垃圾收集器的接口. Python 使用引用记数来跟踪什么时候销毁一个对象; 一个对象的最后一个引用一旦消失, 这个 ...

  3. Python垃圾回收机制及gc模块详解:内存泄露的例子

    标记清理是用来解决循环引用的.分代回收针对所有的新创建即进入0代的对象和进入1.2代的对象..这样就解释了python“引用计数为主.标记清理+分代回收为辅”的垃圾回收原理,因为循环引用毕竟是少数情况 ...

  4. Python之美[从菜鸟到高手]--Python垃圾回收机制及gc模块详解

    http://blog.csdn.net/yueguanghaidao/article/details/11274737

  5. Python垃圾回收机制:gc模块

    在Python中,为了解决内存泄露问题,采用了对象引用计数,并基于引用计数实现自动垃圾回收. 由于Python 有了自动垃圾回收功能,就造成了不少初学者误认为不必再受内存泄漏的骚扰了.但如果仔细查看一 ...

  6. python 内存泄漏调试

    Python应用程序内存泄漏的调试 Quake Lee quakelee@geekcn.org 新浪网技术(中国)有限公司 Sina Research & Development Python ...

  7. python内存泄漏,python垃圾手动回收,1

    部署的舆情系统,内存变大,找原因. 一个小例子. def func(): local_list = list(range(10000000)) func() time.sleep(200) 能够观察到 ...

  8. 说说JVM原理?内存泄漏与溢出的区别?何时产生内存泄漏?

    1.JVM原理 JVM是Java Virtual Machine(Java虚拟机)的缩写,它是整个java实现跨平台的最核心的部分,所有的Java程序会首先被编译为.class的类文件,这种类文件可以 ...

  9. Android应用内存泄漏的定位、分析与解决策略

    什么是内存泄漏 对于不同的语言平台来说,进行标记回收内存的算法是不一样的,像 Android(Java)则采用 GC-Root 的标记回收算法.下面这张图就展示了 Android 内存的回收管理策略( ...

随机推荐

  1. 几句话弄清楚Java参数传值还是传引用

    最近刷题做了一些算法题,对于在递归函数调用的时候什么时候传入值,什么时候传入引用有疑问,在网上搜索了一下,得出了一下三条总结: 1.对象就是传引用 2.原始类型就是传值 3.String,Intege ...

  2. iOS 关于tableView中有多个textField,输入框被遮住的解决方法

    这里采用tableView整体上移的方法. 代码:(其中 60 为 单元格的高度) //点击输入框触发 - (void)textFieldDidBeginEditing:(UITextField *) ...

  3. Codeforces Round #277(Div 2) A、B、C、D、E题解

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud A. Calculating Function 水题,判个奇偶即可 #includ ...

  4. PHP--变量部分知识点

    PHP全局变量 PHP全局变量作用域不同与C,在函数内部不可以使用全局变量,要在函数内部使用全局变量需要,global $var或者使用超全局变量数组$GLOBALS['var']. 静态变量 PHP ...

  5. js抽象类和抽象方法

    js中模拟抽象类:在父类中调用一个未定义的方法,这个方法在子类中必须被实现. 1, 模拟类的工厂模式 //基类 var Class = { //基类的静态方法 creat:function(){ // ...

  6. 让PHP程序永远在后台运行

    PHP里有个函数很有用.这是在最近的开发中才逐渐用到的. int ignore_user_abort ( [bool setting] ) 这个函数的作用是指示服务器端在远程客户端关闭连接后是否继续执 ...

  7. 正则表达式及re模块

    正则表达式使用的特殊符号与字符 元字符 描述 \ 将下一个字符标记符.或一个向后引用.或一个八进制转义符.例如,“\\n”匹配\n.“\n”匹配换行符.序列“\\”匹配“\”而“\(”则匹配“(”.即 ...

  8. window7下statsvn统计代码量

    下载statsvn:http://www.statsvn.org/ 将下载后的statsvn.jar放到d:\svn目录下; 打开cmd窗口切换到需要统计代码的项目目录如:d:\project\web ...

  9. wordpress教程之文章页single.php获取当前文章所属分类

    之所以要发这篇文章,是因为这个方法适用于: WP默认文章分类 手动添加的自定文章分类 插件(custom post type ui)添加的自定义文章分类(含taxonomy) 方法目的:在文章模板中, ...

  10. Java 学习 第一篇

    1:Java文档注释:使用javadoc工具可以提取程序中文档注释来生成API文档:javadoc命令的基本用法:javadoc 选项 java源文件/包javadoc -d {} -windowti ...