Python内存泄漏

摘要:
在《Python内存优化》一文中也提到,该函数能发现可以用slots进行内存优化的对象。

一、Python内存管理

Python中,python中,一切都是对象,又分为mutable和immutable对象。二者区分的标准在于是否可以原地修改,“原地”可以理解为相同的地址。可以通过id()查看一个对象的“地址”,如果通过变量修改对象的值,但id没发生变化,那么就是mutable,否则就是immutable。比如:

#immutable
a=1
print(id(a))#140734991946784
a=3
print(id(a))#140734991946848

#mutable
b =[1,2,3]
print(id(b))#1273753854536
b.append(4)
print(id(b))#1273753854536

1、引用计数

引用计数(References count),指的是每个Python对象都有一个计数器,记录着当前有多少个变量指向这个对象。

通过sys.getrefcount(obj)对象可以获得一个对象的引用数目,返回值是真实引用数目加1(加1的原因是obj被当做参数传入了getrefcount函数),例如:

importsys
    s = 1
    print(sys.getrefcount(s))#1297
    a = 'jkl'
    print(sys.getrefcount(a))#4

从中可以看出,Python中的对象缓存池会缓存十分常用的immutable对象,比如整数1。

2、垃圾回收

这里强调一下,本文中的的垃圾回收是狭义的垃圾回收,是指当出现循环引用,引用计数无计可施的时候采取的垃圾清理算法。

在python中,使用标记-清除算法(mark-sweep)和分代(generational)算法来垃圾回收

3、gc module

这里的gc(garbage collector)是Python 标准库,该module提供了与上一节“垃圾回收”内容相对应的接口。通过这个module,可以开关gc、调整垃圾回收的频率、输出调试信息。gc模块是很多其他模块(比如objgraph)封装的基础,在这里先介绍gc的核心API。

import gc

gc.enable()#开启gc(默认开启)

gc.disable()#关闭gc

gc.isenabled()#判断gc是否开启

gc.collect()#执行一次垃圾回收,不管gc是否处于开启状态都能使用

gc.set_threshold(t0,t1,t2)#设置垃圾回收阈值

gc.get_threshold()# 获得当前的垃圾回收阈值

gc.get_objects()#返回所有被垃圾回收器(collector)管理的对象

gc.get_referents(*obj)#返回obj对象直接指向的对象

gc.get_referrers(*obj)#返回所有直接指向obj的对象

gc.set_debug(gc.DEBUG_COLLECTABLE)#打印可以被垃圾回收器回收的对象

gc.set_debug(gc.DEBUG_UNCOLLECTABLE)#打印无法被垃圾回收器回收的对象,即定义了__del__的对象

gc.set_debug(gc.DEBUG_SAVEALL)#当设置了这个选项,可以被拉起回收的对象不会被真正销毁(free),而是放到gc.garbage这个列表里面,利于在线上查找问题

二、内存泄漏

内存泄漏产生的两种情况:

1、是对象被另一个生命周期特别长的对象所引用。

解决方式:只要在适当的时机解除引用。

2、是循环引用中的对象定义了__del__函数,如果定义了__del__函数,那么Python中的解析器就

不能判断解析对象,所以不会做处理。

解决方法:要么不再使用__del__函数,换一种实现方式,要么解决循环引用。

查找内存泄漏的两个库:gc、objgraph

1、objgraph

objgraph的实现调用了gc的这几个函数:gc.get_objects(), gc.get_referents(), gc.get_referers(),然后构造出对象之间的引用关系。

常用的方法:

1、def count(typename):返回该类型对象的数目,其实就是通过gc.get_objects()拿到所用的对象,然后统计指定类型的数目。

2、def by_type(typename):返回该类型的对象列表。线上项目,可以用这个函数很方便找到一个单例对象。

3、def show_most_common_types(limits = 10):打印实例最多的前N(limits)个对象,这个函数非常有用。在《Python内存优化》一文中也提到,该函数能发现可以用slots进行内存优化的对象。

3、def show_growth():统计自上次调用以来增加得最多的对象,这个函数非常有利于发现潜在的内存泄露。函数内部调用了gc.collect(),因此即使有循环引用也不会对判断造成影响。

4、def show_backrefs():生产一张有关objs的引用图,看出看出对象为什么不释放,后面会利用这个API来查内存泄露

5、def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):找到一条指向obj对象的最短路径,且路径的头部节点需要满足predicate函数 (返回值为True),可以快捷、清晰指出 对象的被引用的情况,后面会展示这个函数的威力

6、def show_chain():将find_backref_chain 找到的路径画出来, 该函数事实上调用show_backrefs,只是排除了所有不在路径中的节点。

2、查找内存泄漏

查找方法,先调用一次objgraph.show_growth(),然后调用怀疑内存泄漏的函数,最后再调用一次objgraph.show_growth(),看看是否有增加对象。

#-*- coding: utf-8 -*-
importobjgraph
da=[]
classa():
    pass

defrun():
    b=a()
    da.append(b)
    ifTrue:
        returnda.remove(b)

if __name__ == '__main__':
    objgraph.show_growth()
    try:
        run()
    except:
        passobjgraph.show_growth()

#显示#function                       2150     +2150#wrapper_descriptor             1096     +1096#dict                           1037     +1037#tuple                           851      +851#builtin_function_or_method      763      +763#method_descriptor               753      +753#weakref                         711      +711#getset_descriptor               393      +393#member_descriptor               307      +307#type                            306      +306#a        1        +1

三、循环引用

如果存在循环引用,那么Python的gc就必须开启(gc.isenabled()返回True),否则就会内存泄露。

1、 定位循环引用

这里还是是用GC模块和objgraph来定位循环引用。需要注意的事,一定要先禁用gc(调用gc.disable()), 防止误差。

这里利用之前介绍循环引用时使用过的例子: a, b两个OBJ对象形成循环引用

importobjgraph,gc

classA(object):
    pass

defrun():
    a,b =A(),A()
    a.attr_b =b
    b.attr_a =a

if __name__ == '__main__':
    gc.disable()
    for x in range(50):
        run()
    objgraph.show_most_common_types(20)

#function                   2150#dict                       1125#wrapper_descriptor         1092#builtin_function_or_method 762#method_descriptor          752#tuple                      670#weakref                    597#getset_descriptor          375#member_descriptor          307#type                       193#cell                       173#list                       152#module                     112#ModuleSpec                 110#A                          100#classmethod                86#set                        79#_NamedIntConstant          74#frozenset                  72#SourceFileLoader           70

在实际项目中,不大可能到处用objgraph.show_most_common_types或者objgraph.by_type来排查循环引用,效率太低。有没有更好的办法呢,有的,那就是使用gc模块的debug 选项。在前面介绍gc模块的时候,就介绍了 # gc: collectable # gc: collectable # gc: collectable " v:shapes="_x0000_s1029">gc.DEBUG_COLLECTABLE 选项,我们来试试。

importgc,time

classA(object):
    pass

defrun():
    a, b =A(), A()
    a.attr_b =b
    b.attr_a =a

if __name__ == '__main__':
    gc.disable()
    gc.set_debug(gc.DEBUG_COLLECTABLE)
    for x in range(1):
        run()
    gc.collect()
    time.sleep(1)

#gc: collectable <A 0x000001BFB9E85A60>#gc: collectable <A 0x000001BFB9E85A00>#gc: collectable <dict 0x000001BFB828FFC0>#gc: collectable <dict 0x000001BFB829F640>

2、消灭循环引用

找到循环引用关系之后,解除循环引用就不是太难的事情,总的来说,有两种办法:

①手动解除与使用weakref。

②手动解除很好理解,就是在合适的时机,解除引用关系

常用的方法:

(1)weakref.ref(object, callback = None)

创建一个对object的弱引用,返回值为weakref对象,callback: 当object被删除的时候,会调用callback函数,在标准库logging (__init__.py)中有使用范例。使用的时候要用()解引用,如果referant已经被删除,那么返回None。比如下面的例子

importweakref
classA(object):
    deff(self):
        print("asd")

if __name__ == '__main__':
    a=A()
    w =weakref.ref(a)
    w().f()
    dela
    w().f()

运行上面的代码,会抛出异常:AttributeError: 'NoneType' object has no attribute 'f'。因为这个时候被引用的对象已经被删除了

(2)weakref.proxy(object, callback = None)

创建一个代理,返回值是一个weakproxy对象,callback的作用同上。使用的时候直接用 和object一样,如果object已经被删除 那么跑出异常 ReferenceError: weakly-referenced object no longer exists。

importweakref
classA(object):
    deff(self):
        print("asd")

if __name__ == '__main__':
    a=A()
    w =weakref.proxy(a)
    w.f()
    dela
    w.f()

(3)weakref.WeakSet

这个是一个弱引用集合,当WeakSet中的元素被回收的时候,会自动从WeakSet中删除。WeakSet的实现使用了weakref.ref,当对象加入WeakSet的时候,使用weakref.ref封装,指定的callback函数就是从WeakSet中删除。感兴趣的话可以直接看源码(_weakrefset.py),下面给出一个参考例子:

importweakref
classA(object):
    deff(self):
        print("asd")

if __name__ == '__main__':
    a=A()
    w =weakref.WeakSet()
    w.add(a)
    print (len(w))#1
    dela
    print (len(w))#0

Python中objgraph模块官方文档网址:https://mg.pov.lt/objgraph/

Python中的gc模块官方文档网址:https://docs.python.org/3/library/gc.html

免责声明:文章转载自《Python内存泄漏》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇js批量下载文件C/C++读写excel文件 的几种方式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

python使用multiprocessing创建多线程时异常的解决

一.linux   1.可能没有此模块,会抛出异常cannot import name Process,需要安装一下,在终端中使用pip install multiprocessing安装即可。   2.注意不要将文件明设置为multiprocessing.py 二.windows   1.注意不要将文件明设置为multiprocessing.py   2...

android 录制

引用:http://www.jtben.com/document/919575 出自ATHZ-SEC1-WIKI   跳转到: 导航, 搜索 Android 事件录制脚本的生成 Software R&D Center-SW R&D Dept.-Sec.1HZSWTL2011008Prepared by: Andy3 LiuJan. 2...

Python正则表达

```# -*- coding:utf-8 -*-import re re - Support for regular expressions (RE).正则表达式是一个特殊的字符序列,它能帮助你方便的检查一个字符串是否与某种模式匹配。re 模块使 Python 语言拥有全部的正则表达式功能。compile 函数根据一个模式字符串和可选的标志参数生成一个正...

python 删除多个同一后缀名文件(基于python 3.X)

import osdef remove(): filearray = [] address_Excel="E:\totally\FinancePDF" f_list = os.listdir(address_Excel)for fileNAME in f_list:# os.path.splitext():分离文件名与扩展名 if os.path.s...

python网络/并发编程部分简单整理

软件开发架构C/S架构:Client与Server客户端与服务器端架构 .exeB/S架构:Browser与Server浏览器端与服务器端架构IP地址: IP地址是指互联网协议地址 IP地址通常用“点分十进制”表示,实际上是32位二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)port: 设备与外界通讯交流的出口...

Python-单元测试 unittest &amp;amp; HTMLTestRunner模块产生的测试报告

 1、单元测试: ——开发程序的人测已经已经写好的代码。 unittest框架,执行的顺序是按照方法名的字母来排序的 setUpClass方法是最开始执行的,只会执行一次 tearDownClass是最后执行的,只会执行一次 setUp方法是在每个测试用例执行前会执行 tearDown方法是在每个用例执行后会执行 import unittest de...