python判断内存泄漏_Python内存泄漏

一、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,gcclassA(object):pass

defrun():

a,b=A(),A()

a.attr_b=b

b.attr_a=aif __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,timeclassA(object):pass

defrun():

a, b=A(), A()

a.attr_b=b

b.attr_a=aif __name__ == ‘__main__’:

gc.disable()

gc.set_debug(gc.DEBUG_COLLECTABLE)for x in range(1):

run()

gc.collect()

time.sleep(1)#gc: collectable #gc: collectable #gc: collectable #gc: collectable

2、消灭循环引用

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

①手动解除与使用weakref。

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

常用的方法:

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

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

importweakrefclassA(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。

importweakrefclassA(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),下面给出一个参考例子:

importweakrefclassA(object):deff(self):print(“asd”)if __name__ == ‘__main__’:

a=A()

w=weakref.WeakSet()

w.add(a)print (len(w))#1

delaprint (len(w))#0

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

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

    原文作者:杨子虚
    原文地址: https://blog.csdn.net/weixin_28782221/article/details/113507766
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞