python项目“内存泄漏”的调试过程
现象:
在压测的过程中,服务消耗的内存不断飙升,使用的内存大大超过了它可能消耗的内存大小
首先是内存泄漏的几个可能原因:
1、存在循环引用,gc不能释放;
2、存在全局对象,该对象不断的变大,占据内存;
3、使用了c或者c++扩展,扩展内存溢出了;
1、首先检查代码,把代码中可能发生内存小泄漏的地方全部修改下、代码中没有调用c或者c++的扩展库
2、查看下gc是否被禁止了
import gc
gc.isenabled()
得到的结果是True,说明gc是开启了的,可以手动执行下垃圾回收看是否会释放内存
gc.collect()
发现可用内存并没有增加,说明的确发生了内存泄漏,这部分内容无法被gc回收
3、使用objgraph查看下引用和对象的生成关系,看下内存消耗前10的对象变化情况
import objgraph
objgraph.show_most_common_types(limit=10)
可以得到如下结果:
function 1246
wrapper_descriptor 1094
builtin_function_or_method 708
method_descriptor 540
dict 496
weakref 361
tuple 243
list 214
member_descriptor 192
getset_descriptor 171
压测一段时间后,top10的内存消耗并没有什么变化
4、使用objgraph.show_growth()、观察对应增长情况
objgraph.show_growth()
发现除了前面几次调用有增长外:
>>> objgraph.show_growth()
function 1246 +1246
wrapper_descriptor 1081 +1081
builtin_function_or_method 708 +708
method_descriptor 540 +540
dict 493 +493
weakref 358 +358
tuple 248 +248
list 214 +214
member_descriptor 187 +187
getset_descriptor 166 +166
>>> objgraph.show_growth()
wrapper_descriptor 1094 +13
member_descriptor 192 +5
getset_descriptor 171 +5
weakref 361 +3
dict 496 +3
在压测一段时间后,上述内容也没有什么明显的变化了,但是此时内存的消耗却仍然在增加
5、是否是循环引用的问题?
如果怀疑某个对象出现了循环引用,可以通过objgraph工具来查看
比如下面这个例子中存在循环引用:
1. # -*- coding: utf-8 -*-
2. import objgraph, sys
3. class OBJ(object):
4. pass
5.
6. def show_direct_cycle_reference():
7. a = OBJ()
8. a.attr = a
9. objgraph.show_backrefs(a, max_depth=5, filename = "direct.dot")
10.
11. def show_indirect_cycle_reference():
12. a, b = OBJ(), OBJ()
13. a.attr_b = b
14. b.attr_a = a
15. objgraph.show_backrefs(a, max_depth=5, filename = "indirect.dot")
16.
17. if __name__ == '__main__':
18. if len(sys.argv) > 1:
19. show_direct_cycle_reference()
20. else:
21. show_indirect_cycle_reference()
通过objgraph.show_backrefs来显示一个对象的引用情况, 上述代码会得到两个文件:
direct.dot、indirect.dot
可以用graphviz工具来打开.dot文件
a、首先下载graphviz工具、解压
b、解压后的目录中有个bin目录
c、打开dotty.exe文件
d、打开后右键,选择load graph,选择上述dot文件,就可以看到引用图,从而查看是否存在循环引用
针对上述代码得到的图如下:
可以看到是存在循环引用的
再看下如下代码:
1. # -*- coding: utf-8 -*-
2. import objgraph, sys
3. class OBJ(object):
4. pass
5.
6. def direct_cycle_reference():
7. a = OBJ()
8. a.attr = a
9.
10. if __name__ == '__main__':
11. direct_cycle_reference()
12. objgraph.show_backrefs(objgraph.by_type('OBJ')[0], max_depth=5, filename = "direct.dot"
得到的引用图如下:
这个是没有循环引用的情况
这个方法我没有用好,没有得出啥有用的结论,出来,但是通过下面的这步,其实也可以判断出代码中并不存在内存泄漏
6、代码中是否存在内存泄漏的判断
其实从top命令的RES一栏中也可以看出来,虽然服务消耗的内存在增长,进程消耗的物理内存RES其实并没有增长,一直都稳定在2.45g没有变化,所以通过这个方式可排除是代码中出现了内存泄漏的情况, 应该是别的某个地方出现了内存泄漏
可以在Handler的post方法中添加一段循环引用的代码看下会出现什么,如下:
@tornado.gen.coroutinedef post(self):a = [i for i in range(1000000)]b = [i for i in range(900000)]a.append(b)b.append(a)
现象:
进程消耗的物理内存RES在不断的上升,这种才是进程内部发生了内存泄漏
同时另外一个现象是,服务很卡顿,应该是在频繁的进行垃圾回收导致的
所以得出的一个结论是,代码中并没有内存泄漏,否则进程的RES值会一直上升的, 看来得从其他方便找问题, 我把post请求中我的代码全部注释掉,只留一句:
self.write('11')
发现问题依然存在,这也证明了,内存泄漏并不是代码导致的。
8、TIME_WAIT的问题
我直接把tornado服务中的核心api全部注释掉,就剩下一个空的tornado服务在跑,如上,内存仍然一直在减少。
此时因为服务端只有空服务,所以响应时间很短,在压测的时候,发现客户端和服务端都出现了大量的TIME_WAIT
TIME_WAIT是客户端才有的状态,为什么服务端会出现大量的TIME_WAIT?
我现在以为是因为大量的连接处于TIME_WAIT状态导致连接没有及时关闭,所以内存消耗一直增加,于是我修改了几个内核参数来解决TIME_WAIT的问题
sysctl -w net.ipv4.tcp_tw_recycle=1 #及时回收
sysctl -w net.ipv4.tcp_tw_reuse=1 #tcp连接重用
客户端和服务端分别执行了这两个命令后, TIME_WAIT都没有了,但是此时压测的时候,发现服务端消耗的内存仍然在持续增加, 那看来不是TIME_WAIT的问题
9、难道问题出现在tornado内部?
于是我换了个tornado的版本试试,发现问题依然存在
目前的版本是5.1.1, 我尝试了最新的6.0.2, 问题依然存在
10、尝试pyrasite工具
这个工具主要是看内存中哪些对象占用内存最多,可以渗透进入正在运行的python程序,动态修改里面的数据和代码
安装:
pip install pyrasite pyrasite-gui
sudo setsebool -P deny_ptrace=off #针对fedora
$pyrasite-memory-viewer <pid>
会得到一个如下图的东西, 这个过程耗时有点长
就可以看到哪个对象占用内存最多了
在压测前和压测后,分别执行了一次,发现得到的结果基本没有什么变化,说明进程中没有新建什么对象,进程本身没有发生内存泄漏
每个字段的含义如下:
Index : 行索引号
Count : 该类型的对象总数
%(Count) : 该类型的对象总数 占 所有类型的对象总数 的百分比
Size : 该类型的对象总字节数
%(Size) : 该类型的对象总字节数 占 所有类型的对象总字节数 的百分比
Cum : 累积行索引后的%(Size)
Max : 该类型的对象中,最大者的字节数
Kind : 类型
11、tracemalloc工具
可以直接看到哪个对象占了最大的空间,调用栈是啥样的,但是对于python2而言要安装的话需要重新编译python,python3内置。
比较麻烦,没有进行尝试
13、尝试gc.collect()
压测一段时间后,尝试手动执行gc
> gc.collect() #0
> gc.garbage #[]
gc.collect()的结果为0,没有回收到有效对象, gc.garbage的结果为[],也没有无法回收的垃圾对象
14、到这里基本可以得出一个结论:
结论: 我的代码其实本身并没有内存泄漏
问题:但是为什么在压测的过程中机器的内存一直在减少?
原因:发生了Copy-on-Write
详细分析:
由于我的tornado服务是在主进程中fork了多个子进程,在主进程中加载了一个几百兆的dict后传入到了Handler对象中,因为这个dict是只读的,代码中不会对他修改,在服务启动的时候,子进程和父进程应该暂时还是共用的一个dict对象, 虽然用top看到的子进程也占用了内存,而且基本和父进程的内存一致,如下:
其实此时,子进程是共用的父进程的内存
随着压测的进行,我在代码中通过如下方式实时获取了dict对象的引用计数,如下:
class SimilarityHandler(tornado.web.RequestHandler):def initialize(self, id_title, sentence_ids, id_sentences):self.id_title = id_title@tornado.gen.coroutinedef post(self):print sys.getrefcount(self.id_title), 'id_title'
发现,该对象的引用计数一直都在变化,可能我们直观的认为,只要这个id_title字典不修改,子进程就不会从主进程中拷贝一份到自己的内存空间,但是python不是c,即使不修改这个dict,只要这个实例的引用计数发生变化,那么还是会发生copy,如上已经证明了这个dict的引用计数一直在变,所以子进程应该是在不断的拷贝父进程的内存到自己的内存空间,从而导致机器的内存不断消耗。 但是最终这个服务消耗的内存肯定不会超过这个值:
主进程消耗内存*(1+子进程数)
如果超过了,那肯定就是真的发生内存泄漏了。
为什么这个dict的引用计数一直在变化?
因为只要读取它,那么它的引用计数就会发生变化。
在压测的过程中,虽然机器的内存一直在减少,但是进程的RES值并没有发生变化,因为这个值已经是它能使用的全部内存了,在压测的过程中子进程复制的新内存已经包含在了这个值中。
参考地址: linux是写时复制,但是python因为有引用计数,这本质上是对底层数据结构的写入,这就导致了Copy-on-Write发发生
https://www.infoq.cn/article/disable-python-gc-mechanism-instagram-performance-increase
参考地址:
https://blog.csdn.net/kelindame/article/details/73008487
https://cloud.tencent.com/info/6a91beb836ca4c4c79f77d08e86fe278.html
http://drmingdrmer.github.io/tech/programming/2017/05/06/python-mem.html
http://cosven.me/blogs/54
https://cloud.tencent.com/developer/article/1115715
python项目“内存泄漏”的调试过程相关推荐
- python生日贺卡制作以及细节问题的解决最后把python项目发布为exe可执行程序过程
python生日贺卡制作以及细节问题的解决最后把python项目发布为exe可执行程序过程 参考文章: (1)python生日贺卡制作以及细节问题的解决最后把python项目发布为exe可执行程序过程 ...
- android matix滤镜,使用MAT (Memory Analyzer Tool)分析Andriod项目内存泄漏
前言: 在上一篇文章介绍了如何使用Android Monitor分析项目查找内存泄漏 ,本篇将介绍如何使用MAT(Memory Analyzer Tool)来分析和查找项目中内存泄漏的地方 MAT介绍 ...
- python会内存泄漏吗_Python内存泄漏和内存溢出的解决方法
Python内存泄漏和内存溢出的解决方法 发布时间:2020-10-30 23:08:34 来源:亿速云 阅读:92 作者:Leah 这篇文章将为大家详细讲解有关Python内存泄漏和内存溢出的解决方 ...
- android 内存检测框架,Android项目内存泄漏检测
关于Android项目的内存泄漏是一个老生常谈的问题,之前一直是在写代码时各种注意,比如IO流要及时关闭,引用的curcor要及时关闭等,这样做确实能规避一部分的内存泄漏,不过还是会有漏网之鱼,因此除 ...
- Pycharm下运行调试Python项目时,当调试既需要给调试的程序传入命令行参数又需要程序在设置的断点处停下里查看变量时的解决方法
今天在调试了一个复杂的Python项目,其中这个项目的调试需要事先从命令行读取参数,并且在调试期间需要再事先设置的断点处停下来.检查相关的变量. 问题是,在Pycharm的Terminal 输 ...
- vue项目内存泄漏、性能优化总结
最近在客户现场发现项目会有内存泄漏的情况,导致操作一天电脑就卡死,现大体总结如下: 1.$route的监听使用场景 2.大对象的使用,是否回收(手动置null) 3.store的使用 4.window ...
- 纯小白新人菜鸟第一次unity VR项目与matlab联动调试过程记录超详细版本2023.3.12
本人是个超级菜鸟,因为项目需要用到unity.matlab并且实现两者联动,才刚开始接触Unity.Matlab,以前只有一点C/C++和Java基础(好几年前学的,只会加减乘除.连dll是什么都不懂 ...
- iOS 对付内存泄漏,来说说我的调试方法
2019独角兽企业重金招聘Python工程师标准>>> 苹果在iOS5推出了ARC(自动引用计数)技术,此模式下编译器会自动在合适的地方插入retain.release.autore ...
- Android内存泄漏分析及调试
2019独角兽企业重金招聘Python工程师标准>>> Android内存泄漏分析及调试 分类: Android2013-10-25 11:31 5290人阅读 评论(5) 收藏 举 ...
最新文章
- LeetCode简单题之学生分数的最小差值
- 漫谈 REST 架构风格
- 微软开发x86模拟器,让Windows for ARM能运行x86应用
- HTML 4.01 符号实体
- C++中4种强制类型转换 ?
- 程式CSCMSV4黑色炫酷DJ音乐门户模板 音乐网源码
- 四种转换方式:自动,强制,Parse,Convert
- c#中结构与类的区别(转)
- 【正则表达式系列】零宽断言
- moodle平台安装及环境配置(包括安装过程详细截图)
- linux 脚本加密工具下载,linux truecrypt 加密工具
- Hibernate主键生成策略
- 计算机课教学常规要求,2020学校教学常规管理制度
- 将数字字符转换成整型数字
- 树莓派系统搭建在PC虚拟机中
- 5.MCScanX 与circos下载、安装、运用
- 保研论坛app服务器网站,保研通论坛 - 中国最大的保研交流社区
- axios 获取本地json文件
- 情报板/路政/枪机/卡口/监控类摄像机
- 安装bitnami-redmine