精华内容
下载资源
问答
  • python 内存泄漏定位

    2019-12-21 15:34:56
    问题初步定位 import tracemalloc tracemalloc.start() ''' 运行代码段 ''' snapshot = tracemalloc.take_snapshot() top_stats = snapshots.statistics("lineno") print("[top 10]") for stat in top_ ...

    问题初步定位

     

    import tracemalloc
    
    
    tracemalloc.start()
    
    
    '''
        运行代码段
    '''
    
    snapshot = tracemalloc.take_snapshot()
    top_stats = snapshots.statistics("lineno")
    print("[top 10]")
    for stat in top_ stats[:10]:
        print(stat)

    这样可以获取当前运行代码中内存开销的最大十条指令。

    from collections import defaultdict
    from gc import get_objects
    
    before = defaultdict(int)
    
    after = defaultdict(int)
    
    
    
    for i in get_objects():
    
        before[type(i)] += 1
    
    
    
    #上面获取的指令语句所在位置
    
    for i in get_objects():
        after[type(i)] += 1
    print([(k, after[k]-before[k]) for k in after if after[k]-before[k]])
    

    这样就可以确定每次循环过程中这带你语句泄露具体多少内存与泄露内存具体类型

    matplotlib 库中 transform.py 下的set_children这个函数在实时画图中会导致严重的内存泄漏。

    先留个坑(目前手动释放函数中的字典可以解决,但会导致绘图最大化后缩小后布局混乱的问题)

     

     

    展开全文
  • python内存泄漏

    2020-06-30 17:09:41
    python 内存泄漏定位 不同的语言有不同定位的方式。对于golang 而言。pprof 工具已经足够了。C,C++,java 更是有自身的监控定位机制。这里单单阐述python的 内存泄漏。 观察 首先 我们可以从监控工具上看到内存的...

    python 内存泄漏定位

    不同的语言有不同定位的方式。对于golang 而言。pprof 工具已经足够了。C,C++,java 更是有自身的监控定位机制。这里单单阐述python的 内存泄漏。

    观察

    首先 我们可以从监控工具上看到内存的异常告警。于是开始定位是什么问题导致的。

    • 登录到具体容器上。
    • ps -auxf 查看具体是哪个进程导致的内存暴涨。(一般也就是单服务容器)

    定位思路

    业务侧定位

    • 最近新上线的代码 通过对比排查
    • 对大表的select 操作,比如需要下载一个很大的excel。中间对数据操作不停的生成新的集合,导致数据量巨大。
    • 全局变量不停增加数据。
    • 程序GC 不能回收对象,针对python 而言,循环引用对象,自己实现了__del__ 方法。
    • python GC 被禁止。

    以上的一些方法都是依靠对代码有一定的熟悉程度。对新增的东西进行防范。

    工具侧定位

    对于进程而言,对正在执行中的进程我们可以使用gdb 的方式attach 到这个进程。而gdb主要也是查找调用堆栈。对内存的展示和统计并不具备特别的优势。

    针对python 的两个工具。没有侵入性

    pyrasite

    Pyrasite is a library and a set of tools for injecting code into running Python programs.

    安装

    这里注意gdb 需要到8 以上。否则无法attach 到进程上。

    pip install urwid 
    apt-get install python-meliae
    apt update; apt install -y gdb(必须升级到8以上)
    

    使用

    包含三个命令行

    • pyrasite
    • pyrasite-shell
    • pyrasite-memory-viewer

    pyrasite -l 显示可以使用的脚本
    很少使用

    pyrasite-memory-viewer

    使用命令pyrasite-memory-viewer pid查看当前进程的最大对象

    pyrasite-memory-viewer <PID>
    

    改命令可以生成对象的详细信息
    (address, size, refs)

    生成文件到/tmp 目录下
    默认是没有排序的,所以我们需要进行排序

    以下脚本对生成的文件进行了排序。

    import json
    
    if __name__ == '__main__':
            path = "/tmp/pyrasite-47-objects.json"
            ans = []
            with open(path, 'r') as f:
                    for l in f:
                            if l:
                                    try:
                                            i = json.loads(l)
                                            # if i['type'] == 'dict':
                                            ans.append(i)
                                    except Exception as e:
                                            print i
    				        
            ans = sorted(ans, key=lambda x: -x['size'])
            with open("ans.json", 'w') as f:
                    for i in ans:
                            f.write("%s \n" % (i))
    

    pyrasite-shell

    使用这种方式可以attach 进去一个进程。

    pyrasite-shell <PID>
    

    常规检测

    import gc
    gc.isenabled()  # 判断gc是否正在工作
    gc.get_dbug()  # 判断gc是否开启了debug 模式
    gc.garbage  # 获取gc无法被释放的对象
    
    import ctypes
    obj = ctypes.cast(<addr_or_id>, ctypes.py_object).value  #通过地址或者id(刚刚json文件中)来获取当前对象,可以知道对应引用的关系链
    

    objgraph

    主要用来看对象的关联关系,配合pyrasite-shell 来使用

    安装

    apt-get install graphviz
    pip install xdot
    

    使用

    import ctypes
    obj = ctypes.cast(<addr_or_id>, ctypes.py_object).value  #通过地址或者id(刚刚json文件中)来获取当前对象,可以知道对应引用的关系链
    import objgraph
    objgraph.show_refs([obj], filename="test.png")
    

    小结

    • pyrasite-memory-viewer 生成对象以及内存消耗
    • 脚本排序 定位前几个消耗比较大的对象
    • pyrasite-shell 通过内存地址解析
    • 生成图片分析调用关系
    展开全文
  • 定位python内存泄漏问题

    千次阅读 2019-07-10 22:57:02
    记一次 Python 内存泄漏的排查 背景 上周使用我的python web框架开发的第二个项目上线了,但是没运行几天机器内存就报警了,8G内存使用了7G,怀疑有内存泄漏,这个...定位内存泄漏 第一步:确定是否有内存泄漏 上...

    记一次 Python 内存泄漏的排查

    背景

    上周使用我的python web框架开发的第二个项目上线了,但是没运行几天机器内存就报警了,8G内存使用了7G,怀疑有内存泄漏,这个项目提供的功能就是一堆机器学习模型,对历史数据进行训练,挑选出最优的5个模型,用作未来数据的预测,所以整个项目有着数据量大,运行时间长的特点,就是把策略的离线工作搬到了线上。

    定位内存泄漏

    第一步:确定是否有内存泄漏

    pympler检查是否有内存泄漏,程序入口处初始化该工具

    from pympler import tracker,summary,muppy
    memory_tracker = tracker.SummaryTracker()
    

    接口返回处打印内存差异,观察内存是否有泄漏

    memory_tracker.print_diff() # 本次内存和上次内存块的差异
    

    我们用的sanic,所以直接在main.py文件添加如下代码:

     from pympler import tracker,summary,muppy
     memory_tracker = tracker.SummaryTracker()
    
     @app.middleware('request')
     async def set_request_id(request):
         log_id = request.headers.get('log-id')
         threading.currentThread().logid = log_id
         gc.collect()
         memory_tracker.print_diff()
    

    然后我们访问接口,多触发几次,不用看前两次,等输出稳定后,如果有内存泄漏是如下输出:

    在这里插入图片描述
    上图显示每次都有4类泄漏对象,一共泄漏约60K的内存

    如果没有内存泄漏,没有数据输出

    在这里插入图片描述

    第二步:确定内心泄漏的代码块

    我们确定程序有内存泄漏后,就想办法定位到代码块,就是我们自己写的代码,通过一步一步debug,注释,returncontinue等方式定位到造成泄漏的代码块,下面的代码块就是遍历所有模型,然后挨个执行训练方法,因为有20多个模型,我不能挨个注释每次对象来定位,卡在这里了。
    在这里插入图片描述

    第三步:确定泄漏点

    tracemalloc定位泄漏点,python3.7.3自带,在main.py中添加如下代码:

    tracemalloc.start(25)
    snapshot = tracemalloc.take_snapshot()
    @app.middleware('response')
    async def print_on_response(request, response):
        global snapshot
        gc.collect()
        snapshot1 = tracemalloc.take_snapshot()
        top_stats = snapshot1.compare_to(snapshot, 'lineno')
        print("[ Top 10 differences ]")
     	for stat in top_stats[:10]:
        	 if stat.size_diff < 0:
    			continue
     		 print(stat)
     	snapshot = tracemalloc.take_snapshot()
    

    继续访问接口,多访问几次,输出如下,直接定位到具体泄漏的代码位置
    在这里插入图片描述

    图中所有的泄漏点都定位到pandas库,但是我用这些文件搜索内存泄漏,都没有搜到相关内存泄漏的问题,所以得寻找谁调用这些地方,以/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py:859为例,我们要找到我们写的代码哪里调用触发泄漏点

    第四步:打印调用链

    看了tracemalloc文档也没找到打印调用链的方法,后来灵机一动直接在这个文件加了下面代码:

    raise Exception("doctorq")
    

    然后在接口里catch异常,添加logging.exception(e),然后触发接口,打印堆栈信息:

    ERROR:root:doctorq
    Traceback (most recent call last):
      File "/home/doctorq/python-dev/scscore/src/forecasting/forecast.py", line 83, in update_method
        n_fraction=n_fraction)
      File "/home/doctorq/python-dev/scscore/src/forecasting/trainer.py", line 113, in training
        n_fraction=n_fraction)
      File "/home/doctorq/python-dev/scscore/src/forecasting/trainer.py", line 205, in train_machine_learning_model
        is_train=True).dropna()
      File "/home/doctorq/python-dev/scscore/src/feature_engineering/features.py", line 34, in get_feature
        history_same_periods=history_same_periods, zero_replace=zero_replace)
      File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 65, in get_feature
        store_and_sku=store_and_sku)
      File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 85, in get_rolling_feature
        rolling_result = self.get_rolling_result(window, rolling_obj, rolling_types)
      File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 169, in get_rolling_result
        rolling_result = self.rolling__(rolling_obj, rolling_type)
      File "/home/doctorq/python-dev/scscore/src/feature_engineering/sale_relate_feature.py", line 190, in rolling__
        return rolling_obj.min()
      File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 1723, in min
        return super(Rolling, self).min(*args, **kwargs)
      File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 1069, in min
        return self._apply('roll_min', 'min', **kwargs)
      File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 879, in _apply
        result = np.apply_along_axis(calc, self.axis, values)
      File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/numpy/lib/shape_base.py", line 380, in apply_along_axis
        res = asanyarray(func1d(inarr_view[ind0], *args, **kwargs))
      File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 875, in calc
        closed=self.closed)
      File "/home/doctorq/.local/share/virtualenvs/scscore-K9x97I77/lib/python3.7/site-packages/pandas/core/window.py", line 858, in func
        raise Exception("doctorq")
    Exception: doctorq
    

    定位到我们代码触发点如下:

    在这里插入图片描述
    调用的就是pandasRolling的一系列方法,然后搜索该方法是否有泄漏问题

    在这里插入图片描述

    第一个链接链接就是说这些方法(rolling.min/max)有泄漏,pandas rolling max leak memory,具体因为啥泄漏的,也没时间细究,反正issue里说回退到0.23.4是没问题的,那么就回退试试:

    pipenv install pandas==0.23.4
    

    然后我们再用pympler定位有没有内存泄漏,pandas内存泄漏的问题是修复,剩下来就省memoryview的小泄漏了,明天继续

    在这里插入图片描述

    总结

    定位的过程略耗时,不过经过这么一折腾,也算是有经验了,各种工具一阵堆,泄漏问题确定-定位代码块-定位泄漏点-搜索已知泄漏点-解决掉。

    展开全文
  • 作为保障业务系统稳定作业的监控组件发生了内存泄漏,自然是非常严重的,所以开始我们的“排查之旅”。 分析 有许多工具提供了对 Python 程序内存状态的分析与导出,这里我们使用 pyrasite,它可以 attach 到一个...

    背景

    我们(指原作者)在工作中使用 DDAgent Ver. 5 作为采集工具进行被管服务器的性能指标采集与上报,并且对 DDAgent 做了一定程度的定制。在几次特性迭代后,发现线上一批运行许久的被管服务器出现内存占用过高。分析问题机器上进程树各节点占用情况,看到 DDAgent 采集进程的内存占用居高不下。

    作为保障业务系统稳定作业的监控组件发生了内存泄漏,自然是非常严重的,所以开始我们的“排查之旅”。

    分析

    有许多工具提供了对 Python 程序内存状态的分析与导出,这里我们使用 pyrasite,它可以 attach 到一个运行中的 Python 程序,生成一份内存快照,并查看当前有哪些对象类型分别占用了多少内存,从大到小排序。

    使用命令非常简单:pyrasite-memory-viewer <PID>,同时会生成一份快照文件:/tmp/pyrasite-<PID>-objects.json

    由于无法提供真实生产中的数据,下文提及的所有数据均来自于问题版本在测试环境中运行 12 小时之后的采样。

    在 pyrasite 提供的 CUI 视图中,我们可以清晰地看到字典类型的对象实例占用内存最多,达到了 3.4MB,有 6621 个实例:

        Total 60350 objects, 223 types, Total size = 10.4MiB (10857450 bytes)
         Index   Count   %      Size   % Cum     Max Kind
    (X)      0    6621  10   3551928  32  32   98584 dict
    ( )      1   21363  35   1817633  16  49    6342 str
    ( )      2     291   0    902640   8  57   12624 module
    ( )      3     918   1    829872   7  65     904 type
    ( )      4    5605   9    717440   6  72     128 code
    ( )      5    5969   9    716280   6  78     120 function
        More...
    

    可惜 pyrasite 的 CUI 界面没有提供进一步数据透视的能力,以查看这么多字典对象到底是哪些,有什么特征。

    但我们也看到,内存快照文件是 JSON 格式的可读文本,打开后为如下结构的内容:

    {"address": 139671453162096, "type": "instance", "size": 72, "refs": [139671565716816, 139671453172632]}
    {"address": 139671453172632, "type": "dict", "size": 1048, "len": 4, "refs": [139671677239136, 29053312, 139671565997664, 139671451015864, 139671677145776, 29056072, 139671677239040, 139671674819024]}
    {"address": 139671674819024, "type": "bool", "size": 24, "value": "True", "refs": []}
    {"address": 139671677239040, "type": "str", "size": 43, "len": 6, "value": "closed", "refs": []}
    {"address": 29056072, "type": "int", "size": 24, "value": 134, "refs": []}
    ...
    

    很容易猜出,每一行表示当前内存中的一个 Python 对象,address 为该对象的内存地址,type 为该对象的类型名,size 是该对象自身占据的内存大小(不出意外该值和 sys.getsizeof 计算所得一致),如果对象类型为 intstrbool 这种 Primitive Type,则通过 value 表示其值,如果对象类型为 strtuplelistdict 等容器类型(按 Python 定义严谨地讲是实现了 __len__ 方法的类型),那么通过 len 表示其元素数量,最后一个 refs 则表示这个对象上所引用其他对象的地址。

    在对该快照文件中的字典对象做简单分析后,得到了一个很重要的情报:6621 个字典对象中有 4884 个都是空字典 ,占比 73.8%。

    不论什么业务场景,在一个正常的 Python 程序实现中,不可能有如此多的空字典。

    想搞清为什么,就得找出在哪里创建了这些空字典对象。

    但是到目前为止 pyrasite 提供的信息都已探索完,要进一步排查,就得“另辟蹊径”。

    定位

    我们针对发生泄漏的场景重新缕下思路,有如下事实和猜测:

    • 一个或多个地方在持续创建空字典对象,并且无法回收它们,导致内存泄漏

    • 内存泄漏量随着时间变化而增长,在指标采集业务中,很可能是在每次采集过程中造成的泄漏,在间隔周期后又重复触发

    • 并未看到当前依赖的 DDAgent 版本有未关闭的相关 Issue,很可能是我们定制过程中引入的 Bug

    但是,哪怕一次最简单的系统基础指标采集,程序所跑过的代码行数(DDAgent 框架代码、采集 Check 插件代码)都在千级规模,想靠人力去分析定位“泄漏点”,如同大海捞针。

    同时我们还面临一个挑战:由于泄漏过程较慢,很难在本地测试环境进行快速复现和分析。

    如何克服上述困难?结合造成泄漏对象很重要的画像——无法回收的空字典,我们或许可以借助 Python 解释器的运行时修改与自省特性来排查。

    即,我们写一段追踪代码,捕捉符合以下特征的对象:

    • 特征 1:字典(dict)类型

    • 特征 2:字典对象长度为 0

    • 特征 3:该对象的引用计数始终大于 0

    对应的解决方案是:

    • 响应特征 1:构造一个字典类型,其:

      • 我们使用的解释器版本为 CPython 2.7.13,所以是 __builtin__ 而不是 3.x 的 builtins

      • 该方案存在一些问题,但在我们这个场景中恰好够用了,后面复盘时再提

      • 在初始化函数 __init__ 中记录自己被实例化时的堆栈信息,通过 traceback 模块完成,这是实现追踪的关键

      • 并通过 __builtin__ 模块进行运行时替换,将内置的 dict 换成该自定义类型,实现全局追踪

    • 响应特征 2:这个最简单,需要时 len(dict_obj) == 0 搞定

    • 响应特征 3:使用 weakref.WeakSet 实现追踪表收集字典对象,通过“弱引用”特性避免追踪代码影响正常对象的回收

    接着,只要定时将追踪表中符合特征的内容进行输出,就可以达到定位创建未回收空字典对象位置的目标:

    # coding: utf-8
    import __builtin__
    import time
    import json
    import weakref
    import traceback
    import threading
    
    # “弱引用”特性的追踪表确保不干扰正常对象的回收
    trace_table = weakref.WeakSet()
    
    
    # 定时输出符合特征的内容
    def exporter():
        while True:
            time.sleep(30)
            print('writing trace infos...')
            # 将追踪表中的空字典收集输出
            empty_dicts = [d.trace_info for d in trace_table if len(d) == 0]
            with open('traceinfo', 'w') as f:
                f.write(json.dumps(empty_dicts))
    
    
    threading.Thread(target=exporter).start()
    
    
    class TraceableDict(dict):
        idx = 0
    
        def __init__(self, *args, **kwargs):
            super(TraceableDict, self).__init__(*args, **kwargs)
            # !!!获取堆栈信息!!!
            self.trace_info = traceback.extract_stack()
            self.trace_hash = TraceableDict.idx
            TraceableDict.idx += 1
            # 将自己加入到追踪表
            trace_table.add(self)
    
        def __hash__(self):
            # 如果不实现 __hash__ 方法,则无法被插入到 WeakSet 中
            return self.trace_hash
    
    
    # !!!这只是为了定位问题!!!
    # !!!平时千万不要这么用!!!
    __builtin__.dict = TraceableDict
    
    print('start tracing...')
    

    这里需要额外提及一下,由于 dict 字典对象没有实现 __hash__ 方法,因此它无法作为 Key 被插入到 dictsetWeakSet 对象中,一句话测试下便知:

    > python -c "{}[{}]=0"
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
    TypeError: unhashable type: 'dict'
    

    为了使其能被顺利插入到 WeakSet 中,这里使用自增 Id 方案做最简单的 Hash 实现。

    接着我们在 DDAgent 的采集模块 collect.py 入口处启用这段追踪代码:

    # coding: utf-8
    # file: collect.py
    import tracer  # 导入即启用
    import signal
    # ...
    

    将采集进程运行一段时间后,我们得到了 traceinfo 文件:

    [
      [
        [".../embedded/lib/python2.7/threading.py",774,"__bootstrap","self.__bootstrap_inner()"],
        [".../embedded/lib/python2.7/threading.py",801,"__bootstrap_inner","self.run()"],
        [".../modules/monitor/bot/schedule.py",51,"run","task.run()"],
        [".../modules/monitor/bot/task.py",50,"run","super(RepeatTask, self).run()"],
        [".../modules/monitor/bot/task.py",18,"run","self.check()"],
        [".../modules/monitor/checks/collector.py",223,"wrapper","_check.run()"],
        [".../modules/monitor/checks/__init__.py",630,"run","self._roll_up_instance_metadata()"],
        [".../modules/monitor/checks/__init__.py",498,"_roll_up_instance_metadata","dict((k, v) for (k, v) in self._instance_metadata))"],
        [".../modules/monitor/tracer.py",33,"__init__","self.trace_info = traceback.extract_stack()"]
      ],
      [
        [".../embedded/lib/python2.7/threading.py",774,"__bootstrap","self.__bootstrap_inner()"],
        [".../embedded/lib/python2.7/threading.py",801,"__bootstrap_inner","self.run()"],
        [".../modules/monitor/bot/schedule.py",51,"run","task.run()"],
        [".../modules/monitor/bot/task.py",50,"run","super(RepeatTask, self).run()"],
        [".../modules/monitor/bot/task.py",18,"run","self.check()"],
        [".../modules/monitor/checks/collector.py",223,"wrapper","_check.run()"],
        [".../modules/monitor/checks/__init__.py",630,"run","self._roll_up_instance_metadata()"],
        [".../modules/monitor/checks/__init__.py",498,"_roll_up_instance_metadata","dict((k, v) for (k, v) in self._instance_metadata))"],
        [".../modules/monitor/tracer.py",33,"__init__","self.trace_info = traceback.extract_stack()"]
      ],
      [
        [".../embedded/lib/python2.7/threading.py",774,"__bootstrap","self.__bootstrap_inner()"],
        [".../embedded/lib/python2.7/threading.py",801,"__bootstrap_inner","self.run()"],
        [".../modules/monitor/bot/schedule.py",51,"run","task.run()"],
        [".../modules/monitor/bot/task.py",50,"run","super(RepeatTask, self).run()"],
        [".../modules/monitor/bot/task.py",18,"run","self.check()"],
        [".../modules/monitor/checks/collector.py",223,"wrapper","_check.run()"],
        [".../modules/monitor/checks/__init__.py",630,"run","self._roll_up_instance_metadata()"],
        [".../modules/monitor/checks/__init__.py",498,"_roll_up_instance_metadata","dict((k, v) for (k, v) in self._instance_metadata))"],
        [".../modules/monitor/tracer.py",33,"__init__","self.trace_info = traceback.extract_stack()"]
      ],
    ...
    

    不用花太多精力,就可以识别到几乎所有的空字典对象都创建自 .../modules/monitor/checks/__init__.py 文件的第 498 行,在一个名为 _roll_up_instance_metadata 的方法中:

    class AgentCheck(object):
        # ...
        def _roll_up_instance_metadata(self):
            self.svc_metadata.append(
                dict((k, v) for (k, v) in self._instance_metadata))
            self._instance_metadata = []
    

    该方法在每个采集过程中都会被调用一次,每次调用将某些元数据插入到 svc_metadata 这个对象成员列表中。

    既然有生产肯定有消费,我们紧接着该方法就找到重置 svc_metadata 列表的代码:

    class AgentCheck(object):
        # ...
        def _roll_up_instance_metadata(self):
            # ...
        def get_service_metadata(self):
            if self._instance_metadata:
                self._roll_up_instance_metadata()  # 注意:这里并不是唯一调用 _roll_up_instance_metadata 的位置
            service_metadata = self.svc_metadata
            self.svc_metadata = []  # 重置
            return service_metadata
    

    如果 get_service_metadata 方法能在每次采集过程末被成功调用,那至少 svc_metadata 不会产生数据堆积。

    但是在检查当前版本的整体实现后,我们并没找到任何一处触发 get_service_metadata 的地方。

    随后,通过对比 DDAgent 官方实现,并审查 Git 提交历史,终于一切真相大白。

    DDAgent 在 checks/collector.py 的第 416 行 调用了 get_service_metadata,对元数据进行了消费:

    class Collector(object):
        # ...
        @log_exceptions(log)
        def run(self, checksd=None, start_event=True, configs_reloaded=False):
            # ...
            # Collect metadata
            current_check_metadata = check.get_service_metadata()  # L416
            # ...
    

    然而我们在某次特性迭代中,为了让 run 方法看上去更整洁,将一些与需求实现无关的代码全部移除了,包括对 get_service_metadata 的调用!

    移除消费代码,但生产代码继续在工作,这就是导致内存泄漏的原因!

    复盘

    这里就不提诸如“做好设计评审与 Code Review”、“加强测试阶段质量检测工作”等“套话”,当然这些也值得我们反思。

    内存泄漏问题几乎不可能彻底预防与治理,像 Rust 这样的安全编程语言也 无法作出承诺保证程序不会发生内存泄漏。

    许多触发内存不安全的行为:数组访问越界、访问释放后的内存等,都可以通过制定更严格的编程模型(如 Rust 提出的所有权+生命周期规则)来规避——甚至可以规避数据竞争(data-race)的问题。

    然而触发内存泄漏的行为,和竞态条件(race-condition)一样,则需要开发人员自己结合开发组件和业务规则进行约束。试想一个需要手动触发 flush 的数据队列,结果我们在不停推送数据的同时却忘了调用它,这种引发的内存泄漏是无法靠任何通用检查规则来甄别的。

    敬畏编码。

    最后聊下我们的“排查之旅”其实非常幸运,因为触发泄漏的关键代码:

    class AgentCheck(object):
        # ...
        def _roll_up_instance_metadata(self):
            self.svc_metadata.append(
                dict((k, v) for (k, v) in self._instance_metadata))
            self._instance_metadata = []
    

    恰好使用 dict 类型构造函数实例化了一个空字典!

    如果直接使用字面量方式创建,如:self.svc_metadata.append({}),则是无法被追踪到的 —— __builtin__ 模块只能替换内置类型构造函数的入口,无法控制字面量。

    假想通过字面量构建空字典的内存泄漏场景,我们又该如何排查?这里提供两个思路,仅作记录:

    • 修改 CPython 源码中 dict 内置类型的实现,根据前面的追踪方案给每个 dict 对象加上实例化时的堆栈信息

    • 后面了解到,CPython 3.4 新增了一个 tracemalloc 模块,虽然还未实践过,但从其官方介绍来看也适用我们这次的场景

      • Compute the differences between two snapshots to detect memory leaks

      • Statistics on allocated memory blocks per filename and per line number: total size, number and average size of allocated memory blocks

      • Traceback where an object was allocated

      • 该模块能提供一个对象被创建时的堆栈信息

      • 逐文件、逐行统计已分配的内存块信息:总大小、数量、平均大小

      • 可以计算两次内存快照间的差异,甄别内存泄漏

    展开全文
  • 用objgraph定位python内存泄漏

    千次阅读 2015-02-02 09:27:15
    http://www.lshift.net/blog/2008/11/14/tracing-python-memory-leaks/ http://mg.pov.lt/objgraph/ http://mg.pov.lt/blog/python-object-graphs.html 注:要生成对象引用图片,不一定要安装xdot,也可以用...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 内存泄漏检测原理

    千次阅读 2018-04-16 00:05:18
    因为往往诸如服务器是需要长期运行的,即便轻微的内存泄漏也将可能带来严重问题。而且这种bug还存在着复现周期长,难以定位的问题。 链接器有个选项–wrap,当查找某个符号时,它优先先解析__wrap_symbol, 解析不...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 所以我测试到出问题的模块还是很快的,定位到了是播放器模块出来问题,我发现只要播放器模块启动,那么内存泄漏,那么就可以确定是播放器模块内部代码有资源重复加载,没有释放。 进一步定位 我通过测试,音乐播放的...
  • 前言 现在高级语言如python, java等,都采用了垃圾收集机制,而不再是像c用户自己需要管理维护内存的方式。自己管理内存极其自由,可以任意申请内存,但如同一把双刃剑,为大量内存泄露...内存泄漏是由于开发人员的...
  • 记一次Pytorch内存泄露的排查与处理

    千次阅读 2020-05-12 23:06:06
    查找了网上的一些内存泄漏排查方法,使用了memory_profiler ojbgraph pympler这三个工具进行排查 参考链接如下: Pytorch超出内存 pytorch内存泄漏分析案例 | list转tensor memory_profiler的使用 python 内存...
  • 在c,c++里管理内存的方式,是由用户自己管理的,用户可以任意申请内存,对于不懂得如何管理和释放垃圾(内存)的用户来说,无疑有造成大量内存泄漏的可能。在java、python这样对于一个字符串、列表、类甚至数值都是...
  • 在PyCharm运行Python代码运行时出现错误导致...其产生的原因也有很多,可能时内存泄漏、依赖的库版本不匹配、环境不满足等。在程序中只能debug逐步定位出错位置,揣测出错原因。 附录几个导致出错的情况: 内存泄漏
  • XX:java/python 1、安卓怎么测试、专项测试关注那些、 2、app安全怎么测试 ...6、性能测试中怎么定位问题、怎么解决问题、如果有内存泄漏怎么定位、java线程堆栈怎么分析; 7、性能测试流程是什么?如何开展性...
  • 知乎面试

    2018-04-29 01:00:56
    -安卓性能怎么测的 内存泄漏问题,如何定位,具体工具是啥 -上线流程,包括打包,面试官更关注如何上灰度 -迭代周期是怎么样的(包括几个阶段,我都会做什么) -上线后,如何跟踪用户反馈,如何处理定位 -...
  • JVM马士兵老师

    2020-07-12 23:29:24
    JVM(java virtual machine) ...忘了回收垃圾会导致内存泄漏,但是java不会,jvm有垃圾回收机制 没有引用指向这个对象,这个对象就是垃圾对象 2.怎么定位垃圾? python中:引用计数:计算有多少..
  • 不能解决循环引用:当几个对象循环引用时,但没有其他引用指向这里面的对象时,这时候叫做几个垃圾,这几个对象每个引用都是1,会发生内存泄漏 Root Searching(跟可达算法) 现在正跑着的线程栈里面的局部变量以及...
  • 暑期开发过程中的一些经验记录

    千次阅读 2014-09-16 21:18:25
    当利用VS2010定位出new出现泄漏后,应该在下方(不一定是当前块内)该new变量不使用后delete; 三、free和delete如何知道需要释放的内存大小 在利用malloc或new分配内存空间的时候,实际分配的空间会比程序员申请的...
  • 内存泄漏优化;线程优化;Bitmap优化;网络优化;懒加载优化,启动页优化;静态变量优化;电量性能优化;view控件异常销毁保存重要信息优化;去除淡黄色警告优化;使用注解替代枚举优化;glide加速优化;多渠道打包...
  • memwatch: 内存泄漏检测和越界检测的轻量库 mimalloc: 微软的内存分配器 mqtt: 客户端服务端架构的发布/订阅模式的消息传输协议 mtmalloc: MT hot memory allocator Nanolog: 高性能纳秒尺度C++日志工具 netmap...
  • 2020-2950 WebLogic T3 payload exploit poc python3|CVE-2020-2883-Weblogic coherence.jar RCE|WebLogic-Shiro-shell-WebLogic利用CVE-2020-2883打Shiro rememberMe反序列化漏洞,一键注册filter内存shell ...
  • 给大家丢脸了,用了三年golang,我还是没答对这道内存泄漏题 内存碎片化问题 chan相关的goroutine泄露的问题 string相关的goroutine泄露的问题 你一定会遇到的内存回收策略导致的疑似内存泄漏的问题 sync.Pool的适用...

空空如也

空空如也

1 2
收藏数 28
精华内容 11
关键字:

python内存泄漏定位

python 订阅