如何定位内存泄漏

介绍本文主要介绍一种通过windbg分析内存泄漏的方法,方法也适用linux 。
这个内存泄漏问题比较经典,我个人认为是自己这么多年bug定位中一个非常好的bug,并且在分析的过程中,也有许多需要思考的地方 。通过该问题的分析,你可以了解到分析内存的基本方法和思路 。
现象后台检测程序在某天上午上报了内存警告,大概就是某程序的提交内存达到了1.0G 。
这里需要解释下:在windows下32位应用程序如果提交内存大于某个阈值,比如我正常程序运行时提交内存最多应该只有500M,当检测程序发现该程序提交内存突然大于1.0G了,说明程序可能出现了内存泄漏 。----当时就是这个进程的提交内存大于1.0G并发生了告警 。
登陆后台查看,了解到如下信息:

  • 该进程已经连续运行了90天
  • 提交内存每天都在持续上涨,从启动到目前为止大概累计上升了800M 。
  • 句柄、线程数等资源均正常

如何定位内存泄漏

文章插图
原图没有了,查看90天的提交内存大致如上
基本上可以确定程序存在内存泄漏,让运维通过工具保存了fulldump,并重启进程(否则内存告警会一直提示) 。
这时候对于有经验的人员,这个问题因为并不没有对生产环境造成影响,且等到问题发生异常时还有比较长时间,所以可以不需要立刻恢复现场,否则当问题无法定位时而现场被破坏,将很难解决问题 。
分析思路
  • 代码review:通过比较上个版本和上上个版本之间的差异,找到内存泄漏的地方 。
的确是可以,但存在几个问题,因为本身每天内存泄漏的非常少,且之前版本大都一个月不到就升级了,不能确定这个问题是否是之前一个版本引入的,也可能是很多个版本前引入的?
其次:这个进程处理的消息类型很多,可能有问题的消息处理早就存在,只是最近一段时间其他服务升级,导致有bug的消息处理模块被触发 。所以以上原因通过review近几个版本并不一定能找到 。
还有,review可能能找到多个泄漏点,但可能存在遗漏的情况,并不是该问题的本质原因,修改后问题还可能存在 。
但这个方法对于有人力富于的公司还是可以的,就让一个同事review代码,还是有效果的 。
  • 静态代码检测工具:
公司没有基础,临时部署时间来不及 。
  • 构建复现环境:由于问题出现原因不知,而复现时间太长,找不到快速复现的方法 。
在平时工作中,通过复现注释代码缩小可疑模块,是我们大都会用的有效方法,但这个场景很难找到复现方法 。
  • 规避问题:通过每周半夜重启程序,规避该问题 。
这个方法在很多公司都存在,因为疑难问题的解决的确非常耗时,所以一般会有一个看门狗程序,在客户不知不觉时重启进程,快速恢复,也是非常常用的方法 。这对于我来说,是下下策,不到万不得已,不会使用,印象中自己没怎么用过 。
  • 通过技能查找问题的根本原因 。
umdh:通过在A时间点获取一个进程内存镜像,然后一段时间出现内存泄漏后,在B时间点再获取一个进程内存镜像,通过比较找到之间的差异 。理论可行,但对于这个问题意义不大,本身进程是一个高并发进程,每秒都要处理上百个消息,内存有上百次的申请和释放,A和B比较后差异会非常大,很难找到真实的内存泄漏模块 。
通过以上思考,在有限人力下,通过windbg分析dump的内存,查找真实内存泄漏是快速并有效的方法,下面我就针对该问题给大家介绍下我的分析思路,最后问题的解决大致花费了半个工作日的时间 。
准备工作当时的dump我保存到了百度网盘 。
  • [下载地址](https://pan.baidu.com/s/1vUjAr7edFTxxcKGnGEaatQ "下载地址")(提取码:11bg)
  • 设置好系统的pdb
e:mylocalsymbols;SRV*e:mylocalsymbols*http://msdl.microsoft.com/download/symbols分析方法C++的release版程序,内存携带的信息是非常有限的,大致就是三个维度:
  • 内存大小:每次malloc申请的大小,通过大小,我们可以找到对应的结构体、类
  • 内存地址内容:通过查看内存地址内容,比如有字符串、有特殊的值,找到申请的模块
  • 内存申请次数:通过每小时申请的频率,可以找到具体的消息类型
下面就是通过这三个维度找到具体的原因 。
查找内存大小打印所有堆块信息
!heap -s


推荐阅读