python 内存泄漏调试
Python-LDAP是什么?
- Python-LDAP是一个第三方的开源项目,主要目标是实现python的LDAP接口, 这是一个由C语言编写的Python扩展模块。
- 该模块的主要功能是把通过C接口调用libldap从中取出的数据,转换成为Python的对象, 除此之外还有逆向转换
Python-LDAP存在的问题
- C接口调用程序内存泄漏
- C接口程序Python引用计数器泄漏
分析可能的泄漏点
- 测试自己编写的程序时,发现程序严重消耗内存。在进行53万次查询后,程序占用的内存增大了4倍
- 怀疑程序存在内存泄漏,经过测试,确认程序占用内存的数量的确随查询量增长而增长。确认存在内存泄漏。
- 判断存在三种可能性:
- Python解释器存在内存泄漏
- 我们自己开发的Python程序未能及时释放必须手动释放的资源
- TwistedCore存在内存泄漏
- 调用的某个Python扩展模块出现内存泄漏
可能性一:Python解释器内存泄漏
- 调试步骤:
- 首先检查了Python的Bug Manager,查看Python 2.4.1 以后,报告的和解决掉的关于Python的内存管理问题
- Python近两年来只有3个关于内存泄漏方面的问题被人报告出来,两个是关于urllib2的。 另一个是关于dict的未能确认的泄漏,但只是出现在对dict进行特殊的使用时。
- 据此判断在我的程序的使用方式下Python解释器出现内存泄漏的可能性很低。
可能性二:Python代码未能释放必须
手动释放的资源
- 调试步骤:
- 手动释放所有不需要持续使用的资源,再次测试
- 内存占用没有变化,依然保持原样
- 说明不是Python代码造成的内存泄漏
可能性三:TwistedCore存在内存泄漏
- 调试步骤:
- 查询了Twisted的Tickets系统
- Twisted确实在近期出现过一些内存泄漏的情况,并且包括一个Twisted 2.1.0未修复的
- 升级Twisted到Twisted 2.2.0
- 再次测试,内存占用仍然没有变化,说明:
- 可能TwistedCore不存在内存泄漏
- 还存在未能发现的TwistedCore内存泄漏
- Twisted代码比较多,结构复杂,先跳过这种可能性
可能性四:某个Python扩展模块出现内存泄漏
- 程序主要依赖的扩展模块就是Python-LDAP
- 该模块存在内存泄漏的可能比较高
- 如何进行调试呢?
内存泄漏调试过程
- 使用分析工具Valgrind对整个应用程序的运行过程进行分析
- 运行命令行
valgrind -v --leak-check=yes --num-callers=256 --logfile=d python d.py
- 发现openldap 2.3.19中libldap.so包含两处内存泄漏
- ldap初始化的时候解析域名时存在的一次性泄漏
- 多线程并发情况下,断开连接时忘记释放的mutex
仍然泄漏
- 对第二个泄漏进行了修复
- 再次测试,发现仍然泄漏
- 此时valgrind的报告中只剩下一个一次性的泄漏了,这个泄漏只有49个字节
- 内存都让谁占了呢?
分析内存占用情况
- 为valgrind增加命令行参数--show-reachable=yes,这将会把还在使用的内存情况也打印出来
- 运行命令行
valgrind -v --show-reachable=yes --leak-check=yes --num-callers=256 \
--logfile=d python d.py
分析结果
- 长时间测试后发现了如下情况:
9676796 bytes in 235920 blocks are still reachable in loss record 32 of 32
at 0x3C032183: malloc (vg_replace_malloc.c:105)
by 0x80D9CA0: _PyObject_GC_Malloc (gcmodule.c:1248)
by 0x80D9D6B: _PyObject_GC_NewVar (gcmodule.c:1279)
by 0x80858D3: PyTuple_New (tupleobject.c:68)
by 0x808D9FF: PyType_Ready (typeobject.c:3167)
by 0x808D9C5: PyType_Ready (typeobject.c:3153)
by 0x807C8DB: _Py_ReadyTypes (object.c:1805)
by 0x80D1BC5: Py_InitializeEx (pythonrun.c:167)
by 0x80D1F8C: Py_Initialize (pythonrun.c:283)
by 0x80558C3: Py_Main (main.c:418)
by 0x805520C: main (python.c:23)
难道是Python解释器泄漏了?
- 根据上述报告分析,大量的内存都由python解释器的gcmodule控制
- 难道是gcmodule的回收机制出现了问题?
Python的Garbage Collector机制
- Python的gc是分代垃圾回收机制,共分三代
- 通过引用计数判断数据单元是否可以回收
- 通过扩展模块gc中的接口可以分析调试垃圾回收的情况
- 具体功能请参见Python手册
透过GC看内存
- Python的gc模块功能还是很强大的
- 使用get_objects( )方法可以取得当前所有不能回收的对象(引用计数不为0)的列表
- 存放在列表中的是所有对象的wrapper
吃内存的老鼠抓住了
- 通常情况下,不能释放的对象不会太多,启动时在2、3万个左右
- 我发现程序长时间运行后,不可释放的对象总数大量增加。
- 最终确认,不可释放的对象数量随查询量增大
- 写个循环把他们都打印出来
- 发现了大量的空白列表对象,并且这些空白的列表不能够被GC回收,只有一种可能性, 它们的引用计数不为0
这些列表那儿来的?
- 在Python代码中创建的对象,使用完毕后引用计数器就会归零
- 我的代码中也已经手动删除了所有可以释放掉的资源,尤其是列表对象
- 严重怀疑,这些东西来自于Python-LDAP扩展模块
关于Python扩展模块
- Python的扩展模块在操作Python对象之后,也需要改变对象的引用计数器
- 扩展模块中的C程序一旦忘记修改或者不能正确的修改引用计数器,这些对象就成了僵尸对象
如何找到bug?
- python-ldap有2k多行C代码,虽说不是太多,但要是找起来也是大海捞针。
- 对Python扩展模块的机制不是很熟悉,很难看出代码的错误
- 给作者写信?这确实不是我们的风格,拿不出来patch,那儿好意思跟大家打招呼:)
- 开发任务时间紧,靠别人不如靠自己
- 不过问题还真是棘手,这种问题连valgrind也查不出来啦
静下心来好好想
- 出问题的应该肯定是Python-LDAP模块了
- 我的程序调用它的次数并不是很多,每次查询调一次
- 现在已经知道泄漏的对象是空白的列表
- 于是...
添加检查点
- 在每个调用python-ldap的语句前后都添加检查点
抓住bug了
针对每次调用添加检查点后,立刻就找到了泄漏点。正是在ldapsearch完毕, 获取ldap查询结果之后,出现了一个空白的列表。经过对那段代码进行分析, 仍然无法确认是那行代码写错了,因为对Python扩展模块操作引用计数器的 方法不是很熟悉。当改用同步方式查询ldap的时候,发现没有泄漏出空白列 表。通过比较这两种情况下代码的执行路径,发现在异步查询的情况下,在 返回的ldap控制码为空的时候,python-ldap返回了一个空白的列表给python 解释器,但是并没有讲这个空白列表的引用计数器减掉,导致这个列表的引 用计数一直不能清零,gc也不能回收这个对象。
问题就出在这里
灾难还没有过去
- 再次进行测试,程序终于不再内存泄漏了,能够健康的运行了
- 马上上线运行!
- 程序再次长大……真是福无双至,祸不单行
不要慌
- 测试的时候,明明没有泄漏了,上线了又不行这是为什么?
- 测试的时候,只测试了查的到的条件。
- 上线后,多数情况下,是查不到这个结果的
再次祭出法宝-添加检查点
这次测试查不到的情况,左测右测,再也没有空白列表了…… 再次打出所有不能释放的对象,发现不能释放的对象并没有增加 也没有太多看起来可疑的僵尸对象。添加检查点失败……
再静心想想
- 这次有什么不同:
- 不能释放的对象没有显著增加,甚至还比刚启动的时候减少了。
- 打印所有的不能释放掉的对象,没有发现大量的相同对象
推测结论
- 结论:这次不是引用计数器泄漏
还记得Valgrind么?
valgrind这次真的救了我们。通过valgrind,我们发现python-ldap的c代码部分存在一个 严重的内存泄漏。当一个ldap记录没有查到的时候,一些struct在没有释放的情况下程序 就直接返回了。
python 内存泄漏调试的更多相关文章
- 【原创】python内存泄漏以及python flask框架莫名coredump
1.python内存泄漏 今天在看服务器上的进程时,用top查的时候,发现一个一直跑的脚本程序内存竟然达到了1.6G,这个脚本我有印象,一开始仅占用20M左右,显然是内存泄漏了. 用gc和objgra ...
- 填坑总结:python内存泄漏排查小技巧
摘要:最近服务遇到了内存泄漏问题,运维同学紧急呼叫解决,于是在解决问题之余也系统记录了下内存泄漏问题的常见解决思路. 本文分享自华为云社区<python内存泄漏排查小技巧>,作者:luti ...
- 【转】Android之内存泄漏调试学习与总结
大家有或经常碰到OOM的问题,对吧?很多这样的问题只要一出现相信大家的想法跟小马的一样,就是自己的应用:优化.优化.再优化!而且如果出现类似于OOM这样级别的问题,根本就不好处理,LogCat日志中显 ...
- 一次python 内存泄漏解决过程
最近工作中慢慢开始用python协程相关的东西,所以用到了一些相关模块,如aiohttp, aiomysql, aioredis等,用的过程中也碰到的很多问题,这里整理了一次内存泄漏的问题 通常我们写 ...
- 一次Java内存泄漏调试的有趣经历
人人都会犯错,但一些错误是如此的荒谬,我想不通怎么会有人犯这种错误.更没想到的是,这种事竟发生在了我们身上.当然,这种东西只有事后才能发现真相.接下来,我将讲述一系列最近在我们一个应用上犯过的这种错误 ...
- python内存泄漏
记录: 一个脚本在连续运行后,使用内存越来越大,在循环后手动添加gc.collect()没有作用. 尝试方法: 去除所有函数中当作参数传入的全局变量 使用全局redis对象,不再当作参数传入 循环末尾 ...
- 记一次内存泄漏调试(memory leak)-Driver Monkey
Author:DriverMonkey Mail:bookworepeng@Hotmail.com Phone:13410905075 QQ:196568501 硬件环境:AM335X 软件环境:li ...
- python内存泄漏,python垃圾手动回收,1
部署的舆情系统,内存变大,找原因. 一个小例子. def func(): local_list = list(range(10000000)) func() time.sleep(200) 能够观察到 ...
- [轉]Android的内存泄漏和调试
一. Android的内存机制 Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似.程序员通过new为对象分配内存,所有对象在java堆内分配空间:然而对象的 ...
随机推荐
- Linux crontab 定时任务命令详解
一.简介 crontab 命令用于设置周期性被执行的指令.该命令从标准输入设备读取指令,并将其存放于 crontab 文件中,以供之后读取和执行.通常,crontab 储存的指令被守护进程激活, cr ...
- C#Excel的导入与导出
1.整个Excel表格叫做工作表:WorkBook(工作薄),包含的叫页(工作表):Sheet:行:Row:单元格Cell. 2.NPOI是POI的C#版本,NPOI的行和列的index都是从0开始 ...
- BZOJ3591: 最长上升子序列
因为是一个排列,所以可以用$n$位二进制数来表示$O(n\log n)$求LIS时的单调栈. 首先通过$O(n^22^n)$的预处理,求出每种LIS状态后面新加一个数之后的状态. 设$f[i][j]$ ...
- JS 特殊字符的魅力
特殊字符的魅力 说在前面—鸭子类型 鸭子类型是动态类型的一种风格,在这种风格中,一个对象有效的语义,不是由继承自特定的类或者实现特定的接口,而是由当前方法和属性的集合决定. “当看到一只鸟走起来像鸭子 ...
- Coder-Strike 2014 - Round 1 E. E-mail Addresses
此题题意就是匹配邮箱,提交时一直在test 14上WA,看了测试用例之后才发现计数用的int溢出,要用long long还是做题经验不够,导致此题未能通过,以后一定要考虑数据量大小 题意是找出邮件地址 ...
- [深入浅出WP8.1(Runtime)]网络编程之HttpClient类
12.2 网络编程之HttpClient类 除了可以使用HttpWebRequest类来实现HTTP网络请求之外,我们还可以使用HttpClient类来实现.对于基本的请求操作,HttpClient类 ...
- HDU 4749 Parade Show(贪心+kmp)
题目链接 题目都看不懂,做毛线...看懂了之后就是kmp出,所有的匹配区间,然后DP可以写,贪心也可以做把,DP应该需要优化一下,直接贪,也应该对的,经典贪心问题. #include<iostr ...
- 通过Ajax post Json类型的数据到Controller
View function postSimpleData() { $.ajax({ type: "POST", url: "/Service/SimpleData&quo ...
- spring源码学习之路---AOP初探(六)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近工作很忙,但当初打算学习 ...
- Poj 3250 单调栈
1.Poj 3250 Bad Hair Day 2.链接:http://poj.org/problem?id=3250 3.总结:单调栈 题意:n头牛,当i>j,j在i的右边并且i与j之间的所 ...