python 内存泄漏及gc模块的使用_python gc_zhou191954的博客-程序员秘密

技术标签: Python  

在 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()

#--------------- code end ----------------

    在 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 的垃圾回收时间间隔、甚至主动关闭垃圾回收机制,从而提高运行效率。


推荐阅读文献

1, Python垃圾回收算法描述,http://wiki.woodpecker.org.cn/moin/python_ref_circle_gc
2, Garbage Collection for Python,http://arctrix.com/nas/python/gc/
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhou191954/article/details/9365461

智能推荐

Android传感器源码学习_安卓开发传感器代码_qq_43294789的博客-程序员秘密

Android传感器源码学习系统中,传感器系统的Java部分的实现文件路径系统中,传感器系统的Frameworks层的代码路径系统中,传感器系统的JNI部分的代码路径文件android_hardware_SensorManager.cpp的功能是实现文件SensorManager.java中的Native函数,通过调用SensorManager.cpp和SensorE...

html语言中标签的class属性的作用_html中class标签的作用_陆老师Peter的博客-程序员秘密

其实class属性的值对应的就是你定义的css的名字,它和class的属性值相对应。例如:&lt;td class="t1"&gt;&lt;/td&gt;&lt;style type="text/css"&gt;.t1 {font-size: 12px;//字体大小line-height: 22px;//文本行高color: #000000;//文本颜色margin: 6px;//...

程序员学金融-金融科普(5)-净利润_阿饭同学的博客-程序员秘密

首先需要声明,本文纯属一个毫无远见和真才实学的小小散户的愚昧见解,仅供参考。净利润(收益)是指在利润总额中按规定交纳了所得税后公司的利润留成,一般也称为税后利润或净利润。净利润的计算公式为:净利润=利润总额×(1-所得税率)净利润是一个企业经营的最终成果,净利润多,企业的经营效益就好;净利润少,企业的经营效益就差,它是衡量一个企业经营效益的主要指标。注意: 净利润因为涉及的科目和利益较多,因此造假

python3中的max函数_Python3 max() 函数_瀚忄的博客-程序员秘密

Python3 max() 函数描述max() 方法返回给定参数的最大值,参数可以为序列。语法以下是 max() 方法的语法:max(x,y,z,....)参数x -- 数值表达式。y -- 数值表达式。z -- 数值表达式。返回值返回给定参数的最大值。实例以下展示了使用 max() 方法的实例:#!/usr/bin/python3print("max(80,100,1000)...

教程,word导出为pdf既要书签和链接又要高清图片_word模板导出 用书签_cbszju的博客-程序员秘密

一、谁需要这个教程:用word写论文等转成PDF,既要保留书签和超链接,又要保留高清图片。这个教程应该是全网首发,google都找不到我这样的解决办法。二、必备条件1、adobe acrobat 本人用的是破解版,adobe acrobat X ,完全安装模式安装。2、adobe acrobat 参考文章https://blog.csdn.net/weixin_30568715/article/details/96413531 安装好autobookmark 插件。装完工具栏会有这个增效工具出现

Oracle partition 表分区详解_爱游戏爱动漫的肥宅的博客-程序员秘密

随着表中行数的增多,管理和性能影响也将随之增加。备份将要花费更多时间,恢复也将 要花费更多的时间,对整个数据表的查询也将花费更多时间。通过把一个表中的行分为几个部分,可以减少大型表的管理和性能问题,以这种方式划分发表数据的方法称为对表的分区。

随便推点

以算法岗为例:我最想对入职前的自己说些什么?_zenRRan的博客-程序员秘密

来自:机器学习与推荐系统进入算法岗的正式工作虽然只有半年多,但从 19 年 1 月就在算法岗开始实习,算下来到现在都有两年多的工作经验了,不过正式参加工作以后还是有了很多新的感悟,这篇文章...

DatePicker和TimePicker在5.X的使用_小导弹的博客-程序员秘密

之前遇到问题都是自己做做笔记记录下来了也懒得写博客,后来发现好多公司都会看你的博客,都不在意你的笔记。最近听好多朋友说 找工作要硬东西,不会只听你说技术,这才感到博客的重要性。索性以后有时间就把碰到的问题写写吧,简单归简单,重在帮助需要的人。(网上没有的效果我也特别想写)DatePicker和TimePicker在5.X显示和4.X显示同样效果做时间选择器的都知道正常的配置在4.X和5.X是不一样

VS Code编译Latex避坑指南_KerwinKong的博客-程序员秘密

为了顺利毕业,当年毕业前曾经尝试了诸多的Latex编辑器和编译器,有Texlive自带的Texworks editor,也有自带大量数学符号省去查找麻烦的Texstudio,以及心血来潮下尝试的Atom,到现在需要重新拾起Latex时发现前两者的编辑感受着实不太令人满意,而Atom由于过多的插件安装,导致启动速度实在不尽如人意,最后选择了新方案:VS Code+Texlive+SumatraPDF...

Ext-Grid-HttpProxy-分页_weixin_30483495的博客-程序员秘密

1、客户端代码&lt;%/***@authorlzb*Createdon2008-11-29*练习使用Ext-form-grid**/%&gt;&lt;%@pagecontentType="text/html;charset=UTF-8"%&gt;&lt;%@includefile="http://www.cnblog...

使用gitlab runner 进行CI(三):使用sonarqube做c++的静态检查_sonarqube c++代码检查_CodingForLove的博客-程序员秘密

1. gitlab-ci.yml的配置1.1 几个基本概念1.2 使用CI进行代码检查demo2. Sonarqube安装和配置2.1 Sonarqube安装2.2 数据库配置2.3 sonar-scanner安装2.4 sonar-cxx插件安装2.4 启动2.5 gitlab插件安装2.6 创建项目3. Gitlab-CI配置4. 进阶配置4.1 规则配置4.2 阈值设置4.3 设置自动将扫描结果以评论形式写到gitlab结语前两次我们讲了如何配置gitlab runner(坑挖了挺久,因为是挺久前弄的

2021-05-14 Redis面试题 简单描述下Redis线程模型_微软MVP Eleven的博客-程序员秘密

简单描述下Redis线程模型Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)

推荐文章

热门文章

相关标签