zoukankan      html  css  js  c++  java
  • 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函数),例如:

    import sys
        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 -*-
    import objgraph
    da= []
    class a():
        pass
    
    def run():
        b=a()
        da.append(b)
        if True:
            return
        da.remove(b)
    
    if __name__ == '__main__':
        objgraph.show_growth()
        try:
            run()
        except:
            pass
        objgraph.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对象形成循环引用

    import objgraph,gc
    
    class A(object):
        pass
    
    def run():
        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 选项,我们来试试。

    import gc,time
    
    class A(object):
        pass
    
    def run():
        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。比如下面的例子

    import weakref
    class A(object):
        def f(self):
            print("asd")
    
    if __name__ == '__main__':
        a=A()
        w = weakref.ref(a)
        w().f()
        del a
        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。

    import weakref
    class A(object):
        def f(self):
            print("asd")
    
    if __name__ == '__main__':
        a=A()
        w = weakref.proxy(a)
        w.f()
        del a
        w.f()

    (3)weakref.WeakSet

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

    import weakref
    class A(object):
        def f(self):
            print("asd")
    
    if __name__ == '__main__':
        a=A()
        w = weakref.WeakSet()
        w.add(a)
        print (len(w))# 1
        del a
        print (len(w))# 0

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

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

  • 相关阅读:
    nodejs express搭建一个网站整理
    nodejs http post 请求带参数
    express respond.send 和 end的区别
    .net程序员做的第一个安卓APP
    angularjs ui-grid如何动态设置行高
    错误处理(Operation Result)方法
    jquery validation yyyy-MM-dd格式日期在ie中无法验证通过
    PAT (Basic Level) Practise (中文)- 1010. 一元多项式求导 (25)
    PAT (Basic Level) Practise (中文)- 1007. 素数对猜想 (20)
    PAT (Basic Level) Practise (中文)- 1012. 数字分类 (20)
  • 原文地址:https://www.cnblogs.com/dcpb/p/14267499.html
Copyright © 2011-2022 走看看