python 64式: 第22式、python 垃圾回收机制与内存泄露问题分析

1 python垃圾回收机制


1.1python垃圾回收机制使用引用计数

引用计数是因为变量是对内存数据区域引用。

1.2 python给对象(内存中区域)维护引用计数


创建,复制,计数+1
引用销毁,计数-1
减为0,就释放该内存空间。
作为参数传递给函数时,先复制引用,所以计数+1
del语句消除引用关系,没有_self自我引用,则内存释放

1.3 标记-清除回收机制


解决的问题:循环引用
解决思路: 内存分为两部分,一部分保存真实的引用计数,
         另一部分作为引用计数的副本,在副本上进行引用计数的试验


1.4 分代回收


提升垃圾回收效率
垃圾检测->垃圾回收
同一代对象:垃圾回收频率相近的对象
判断对象的代:根据生存时间。
代码持续访问的活跃对象,会从零代连表转移到一代,再转移
到二代,python处理零代最为频繁,其次一代

1.5 python垃圾回收机制总结


采取是:引用计数为主,标记-清除+分代回收为辅的回收策略
局限:
1) 对于循环引用无效,需要垃圾回收模块gc
2) 重载类的__del__方法,指出了删除对象释放内存空间以外的操作
del语句先查看待删除对象引用计数如果为0,就释放内存并执行__del__方法。
collect方法默认不对重载了__del__方法的循环引用对象回收

2 python内存分析工具及用法


2.1 查看对象的引用


sys.getrefcount(xxx):
  作用:查看对象xxx有几个引用
gc.collect([generation])
作用:如果没有参数,进行一次全面的回收
gc.garbage
是一个列表,每个元素是才能在的垃圾对象

2.2 pyrasite


作用: 连接运行的python程序,类似交互终端
1) 安装:
gdb安装检测:
直接输入gdb
如果没有按转gdb,执行如下命令安装
yum install gdb -y
pip install pyrasite
pip install Cython
yum install wget -y
wget https://files.pythonhosted.org/packages/98/c6/7fa12062ddfe1732d43b34b64a3fe99da958a88fa1d8b7550fe386a9ca01/meliae-0.4.0.tar.gz

tar -xvzf meliae-0.4.0.tar.gz
cd meliae-0.4.0
python setup.py install

2) 检查步骤
pyrasite-memory-viewer pid
作用:查看该进程中各种类型对象数量和内存占用情况
pyrasite-shell pid
作用: 进入到进程的shell

2.3 meliae


作用:把某个时可的内存dump到文件中,然后再对该文件分析,
1)安装
yum install -y python-meliae
2)使用
from meliae import loader
import meliae.scanner
def dumpMemoryByMeliae():
    jsonPath = "./dumpMemory.json"
    meliae.scanner.dump_all_objects(jsonPath)


def analyseDumpMemory():
    memoryJson = loader.load('./dumpMemory.json')
    # 计算对象引用关系
    memoryJson.compute_parents()
    # 去除对象的__dict__属性
    memoryJson.collapse_instance_dicts()
    # 分析内存占用情况
    print memoryJson.summarize()
3)分析占据大内存的对象名称和值
Total 20523 objects, 116 types, Total size = 3.2MiB (3314994 bytes)
Index   Count   %      Size   % Cum     Max Kind
    0     617   3    775640  23  23   49432 dict
    1    8315  40    684262  20  44   12479 str
    2      99   0    394224  11  55   12624 module
其中看Count指标,如果很大,有可能内存泄漏
json文件:
{"address": 140637996210048, "type": "str", "size": 44, "len": 7, "value": "_remove", "refs": []}
其中address字段来自于, id(obj)的结果
id(obj)可以获取对象obj的内存地址

3.1)根据id即python中对象地址来获取该对象的值
import ctypes
def getVariableValue(objId):
    result = ctypes.cast(objId, ctypes.py_object).value
    return result
    
3.2)根据id即python中对象地址来获取该对象的变量名称
import ctypes
import gc
def getVariableName(objId):
    result = gc.get_referrers(ctypes.cast(objId, ctypes.py_object).value)
    return result



2.4 objgraph


objgraph:绘制python中对象图的包
ref:
https://www.baidu.com/link?url=lewTusnGDWqMj-89M8bYv2tY4mYG8yG4GCLxEkTZ7IpJWA95jL2Jq-QmDNM_ZTQo&wd=&eqid=cb75f2200006d61e000000055b7d12d5

1)安装
pip install objgraph
2)使用
import objgraph
objgraph.show_growth()
得到基准对象个数,输出增长的对象
objgraph.show_backrefs()


2.5 guppy


查看python对象占用堆内存大小
1)安装
pip install guppy

2)使用
from guppy import hpy

def getHeapInfo():
    result = hpy().heap()
    return result

3)结果示例
Partition of a set of 26171 objects. Total size = 3346832 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  11916  46   943536  28    943536  28 str
     1   5878  22   474480  14   1418016  42 tuple
     2    324   1   280032   8   1698048  51 dict (no owner)
     


2.6 pympler


作用:统计内存里面类型使用,获取对象大小
ref:
https://baijiahao.baidu.com/s?id=1576773427048442678&wfr=spider&for=pc
1)安装
pip install pympler

2)使用
from pympler import asizeof

def getObjMemory(obj):
    result = asizeof.asizeof(obj)
    return result

总结:
1 建议使用Pyrasite和meliae,Pyrasite可以连接到运行的进程,
meliae可以dump内存,并分析内存中对象,根据对象id反过来
2 找到该对象名称
建议先用Pyrasite-shell连接到进程,然后使用meliae
来dump出这个进程内存信息

3 python内存泄漏原因分析


ref:
https://www.douban.com/note/645539928/

原因1: 对象被另一个声明周期特别长的对象引用
例如: 网络中的长连接

原因2: 循环引用中的对象定义了__del__函数
<<程序员必知的Python陷阱与缺陷列表>>中介绍
如果定义了__del__函数,循环引用中python解释器
无法判断析构对象的顺序,就不处理

4 python内存分析相关代码

import ctypes
import gc
import os
import platform
import sys

from meliae import loader
import meliae.scanner

class People():
    def __init__(self):
        pass


def computeReferenceNumber():
    t = People()
    k = People()
    t._self = t
    print "t reference number: %d" % (sys.getrefcount(t))
    print "k reference number: %d" % (sys.getrefcount(k))


def circularReference():
    a = [1]
    b = [2]
    a.append(b)
    b.append(a)
    print "a reference number: %d" % (sys.getrefcount(a))
    print "b reference number: %d" % (sys.getrefcount(b))
    del a
    del b
    # print "a after del reference number: %d" % (sys.getrefcount(a))
    # print "b after del reference number: %d" % (sys.getrefcount(b))
    try:
        sys.getrefcount(a)
    except UnboundLocalError:
        print "a is invalid"
    # 强制进行垃圾回收
    unreachableCount = gc.collect()
    print "unreachable count: %d" % (unreachableCount)


class A():
    def __init__(self):
        pass

    def __del__(self):
        pass


class B():
    def __init__(self):
        pass

    def __del__(self):
        pass


def memoryLeak():
    a = A()
    b = B()
    a._b = b
    b._a = a
    # del会检查该对象引用计数,如果为0,就执行待删除对象的__del__方法
    del a
    del b
    print gc.collect()
    result = gc.garbage
    print result
    print type(result)


def dumpMemoryByMeliae():
    path = "./dumpMemory.json"
    meliae.scanner.dump_all_objects(path)


def analyseDumpMemory():
    memoryJson = loader.load('./dumpMemory.json')
    # 计算对象引用关系
    memoryJson.compute_parents()
    # 去除对象的__dict__属性
    memoryJson.collapse_instance_dicts()
    # 分析内存占用情况
    print memoryJson.summarize()


def process():
    # computeReferenceNumber()
    # circularReference()
    # memoryLeak()
    dumpMemoryByMeliae()
    analyseDumpMemory()


if __name__ == "__main__":
    process()

参考:
[1] https://www.cnblogs.com/franknihao/p/7326849.html
[2] https://www.cnblogs.com/pinganzi/p/6646742.html
[3] https://www.douban.com/note/645539928/
[4] https://baijiahao.baidu.com/s?id=1576773427048442678&wfr=spider&for=pc
[5] https://www.baidu.com/link?url=lewTusnGDWqMj-89M8bYv2tY4mYG8yG4GCLxEkTZ7IpJWA95jL2Jq-QmDNM_ZTQo&wd=&eqid=cb75f2200006d61e000000055b7d12d5

猜你喜欢

转载自blog.csdn.net/qingyuanluofeng/article/details/88257183
今日推荐