热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

利用进程信息追查内存泄漏_PHP教程

利用进程信息追查内存泄漏。利用进程信息追查内存泄漏摘要:内存泄漏是后台服务器程序经常遇见的软件问题,定位内存泄漏的方法有很多,例如valgrind,但需要重启

利用进程信息追查内存泄漏


摘要:内存泄漏是后台服务器程序经常遇见的软件问题,定位内存泄漏的方法有很多,例如valgrind,但需要重启进程。在某些场合下,重启进程后复现相同的内存泄漏比较困难,或时间较漫长。本文探讨一种利用现有已经发生内存泄漏的进程实例进行分析,尝试获得内存泄漏点的方法。

一、问题现象

Bigpipe是Baidu公司内部的分布式传输系统,其服务器模块Broker采用异步编程框架来实现,并大量使用了引用计数来管理对象资源的生命周期和释放时机。在对Broker模块进行压力测试过程中,发现Broker长时间运行后,内存占用逐步变大,出现了内存泄漏问题。

二、初步分析

针对近期Broker的升级改造点,确定Broker中可能出现内存泄漏的对象。Broker新增了监控功能,其中一项是对服务器各个参数的监控统计,这必然对参数对象有读取操作,每次操作都将引用计数“加一”,并在完成操作后“减一”。当前,参数对象有数个,需要确定是哪个参数对象泄漏了。

三、代码&业务分析

1. 为证明之前的初步分析的结果,可能的方法有是:使用Valgrind运行Broker并启动压力程序复现可能的内存泄漏。但是,使用这种方法:

1) 由于内存泄漏的触发条件并不简单,可能导致复现周期很长,甚至无法复现同样的内存泄漏;

2) 内存泄漏的对象放置在容器中,valgrind正常退出后不报告相关的内存泄漏;

经过另外的测试集群短时间的运行尝试进行复现,果然Valgrind报告未出现异常。

2. 分析现有拥有的条件:幸好,出现“内存泄漏”问题的Broker进程仍然在运行中,真相就在这个进程内部。应该充分利用已有的现场,完成问题的定位。初步希望使用GDB调试。

3. 挑战:使用GDB attach pid的方法将会导致进程挂起,按Broker的设计,一当配对另一个主/从Broker不互相发送心跳, Broker也将自动退出程序,退出后现场就无法保存,这意味着使用GDB的机会只有一次。

4. 方案:利用gdb打印内存信息并从信息中观察可能的内存泄漏点。

5. 步骤一:pmap -x {PID}查看内存信息(如:pmap -x 24671);得到类似如下信息,注意标记为anon的位置:

SHAPE \* MERGEFORMAT

24671: ./bin/broker

Address Kbytes RSS Anon Locked Mode Mapping

0000000000400000 11508 - - - r-x-- broker

000000000103c000 388 - - - rw--- broker

000000000109d000 144508 - - - rw--- [ anon ]

00007fb3f583b000 4 - - - rw--- libgcc_s-3.4.5-20051201.so.1

---------------- ------ ------ ------ ------

total kB 610180 - - -

6. 步骤二:启动gdb ./bin/broker并使用 attach {PID}命令加载现有进程;例如上述进程号为24671,则使用:attach 24671

7. 步骤三:使用setheight 0setlogging on开启gdb日志,日志将存储于gdb.txt文件中;

8. 步骤四:使用x/{内存字节数}a {内存地址} 打印出一段内存信息,例如上述的anon为堆头地址,占用了144508kb内存,则使用:x/18497024a0x000000000109d000;若命令行较多,可以在外围编辑好命令行直接张贴至gdb命令行提示符中运行,或者将命令行写到一个文本文件中,例如command.txt中,然后再gdb命令行提示符中使用 sourcecommand.txt来执行文件中的命令集合,下面是command.txt文件的内容;

SHAPE \* MERGEFORMAT

set height 0

set logging on

x/18497024a 0x000000000109d000

x/23552a 0x000000317ae09000

x/2048a 0x000000317b65e000

x/512a 0x000000318a821000

x/2560a 0x000000318b18d000

9. 步骤五:分析gdb.txt文件中的信息,gdb.txt中的内容如下:

SHAPE \* MERGEFORMAT

0x1071000 <_ZN7bigpipe13bmq_handler_t16_heart_beat_bodyE+832>: 0x0 0x0

0x1071010 <_ZN7bigpipe13bmq_handler_t16_heart_beat_bodyE+848>: 0x0 0x0

0x10710c0 <_zgvz5getippce4lock>: 0x0 0x0

0x10710d0 <_zgvzn7bigpipe13bmq_handler_t14get_heart_beaterie4__sl>: 0x0 0x0

0x10710e0 <_zst8__ioinit>: 0x0 0x0

0x10710f0 <_zgvz5getippce4lock>: 0x0 0x0

0x22c2f00: 0x10200d0 <_ZTVN7bigpipe14BigpipeDIEngineE+16> 0x4600000001

0x22c2f10: 0x1 0x117087b

0x22c2f20: 0x0 0x1214495

0x22c2f70: 0x0 0x0

0x22c2f80: 0x0 0x0

0x22c2f90: 0x0 0x0


Gdb.txt中内容的说明和分析:第一列为当前内存地址,如0x22c2f00;第二、三、四列分别为当前内存地址对应所存储的值(使用十六进制表示),以及gdb的debug的符号信息,例如:0x10200d0<_ZTVN7bigpipe15BigpipeDIEngineE+16>0x4600000001,分别表示:“前16字节”、“符号信息(注意有+16的偏移)”、“后16字节”,但不是所有地址都会打印gdb的debug符号信息,有时符号信息显示在第三列,有时显示在第二列。上述这行内存地址0x22c2f00 存储了bigpipe::BigpipeDiEngine 类的生成的其中一个对象的虚析构函数的函数指针,即虚函数表指针(vptr),其中地址0x10200d0附近内存存储的应该是BigpipeDiEngine类的虚函数表(vtbl),如下所示:

SHAPE \* MERGEFORMAT

(gdb) x/a 0x10200d0

0x10200d0 <_ZTVN7bigpipe15BigpipeDIEngineE+16>: 0x53e2c6

(gdb) x/i 0x53e2c6

0x53e2c6 : push %rbp

(gdb) x/a 0x53e2c6

0x53e2c6 : 0xec834853e5894855

地址0x10200d0中的值是指向BigpipeDiEngine类的析构函数的地址,即真正的析构函数代码段头地址0x53e2c6。可以从上述执行结果看到,地址0x53e2c6的“符号信息”是析构函数名,其汇编命令为push。因此,可以知道最初看到的0x22c2f00地址是对象的一个虚析构函数指针,并且有“符号信息”BigpipeDIEngine显示出来,可以根据这种信息确定出这个类(带虚析构函数的类)生成了多少个实例,然后根据排出来的实例个数做进一步判断。
因此,对gdb.txt排序并做适当处理获得符号(类名/函数名称)出现的次数的列表。例如将上述内容过滤出带尖括号的“符号信息”部分并按出现次数排序,可以使用类似如下命令,catgdb.txt |grep "<"|awk -F '<' '{print $2}' |awk -F '>''{print $1}' |sort |uniq -c|sort -rn > result.txt,过滤出项目相关的变量前缀(如bmq、Bigpipe、bmeta等)cat result.txt|grep -P"bmq|Bigpipe|bigpipe|bmeta"|grep "_ZTV" > result2.txt,获得类似如下的列表:

SHAPE \* MERGEFORMAT

35782 _ZTVN7bigpipe14CConnectE+16

282 _ZTVN3bsl3var4IVarE+16

179 _ZTVN7bigpipe19bmeta_stripe_info_tE+16

26 _ZTV13AutoKylinLockI5MutexE+16

21 _ZTVN6google8protobuf8internal26GeneratedMessageReflectionE+16

8 _ZTVN6comcfg17ConstraintLibrary12WrapFunctionE+16

8 _ZTVN3bsl3var11BasicStringINS_12basic_stringIcNS_14pool_allocatorIcEEEEEE+16

6 _ZTVN7bigpipe19bmeta_broker_info_tE+16

6 _ZTVN7bigpipe15BigpipeDIEngineE+16

10. 然后找出和本工程项目相关的且出现次数最多的为CConnect对象;判断出可能泄漏的对象后,还需要定位在异步框架下,哪个引用计数出现了问题导致CConnect对象无法正常减一并得到释放。

11.经过追查新增的“监控”功能与CConnect相关的代码,如下。

SHAPE \* MERGEFORMAT

if (atomic_add (&_count, -1) == 0) {

_free(_conn)

}

四、真相大白

查看atomic_add函数的实现(如下),可以得知,返回值是自增(减)之前的值,而由于函数名称atomic_add并未特别的表现出这样的含义,导致调用者误用了这个函数,认为是自增之后的值,最终引用计数误认为不为0,导致未执行_free操作,进而导致内存泄漏。通常,和__sync_fetch_and_add对应的函数还有__sync_add _and_fetch,这两者的区别在于“先获得值再加”还是“先加值在获取”。

SHAPE \* MERGEFORMAT

atomic_add(volatile int *count, int add)

{

register int __res;

__res = __sync_fetch_and_add(count, add);

return __res;

}

五、解决方案

因此,程序的改进如下:

SHAPE \* MERGEFORMAT

if (atomic_add_and_fetch (&_count, -1) == 0) {

_free(_conn)

}

六、总结

1. 由于异步框架实现的程序对问题定位跟踪难度较高,需要综合:日志,gdb,pmap等手段完成问题复现和定位;

2. Valgrind检测内存泄漏并不是唯一的方法,且具有一定的局限性;

3.函数名称定义尽量直观表明函数功能,能够避免调用方的一部分错误;

4.应当仔细阅读库函数的说明文档,了解使用方法;

本方法运用的场景和局限:1)使用gdb打印内存信息中,必须符合实例数和内存信息符号有一对一关系的情形,上述实践中CConnect类有虚析构函数,因此在内存信息中能查看到虚函数表指针,且和出现的符号有一一对应的关系,由此能作为内存泄漏存在于此类的推测条件;若泄漏的内存在内存信息中没有留下“痕迹”则无法获得内存泄漏的有效信息;2)在线下尝试内存泄漏复现失败后,但有内存泄漏的进程(现场)在线上仍然存在,可以尝试使用上述方法,从已有的进程(现场)中更多获取内存泄漏信息;3)此方法可以利用现有的已经产生内存泄漏的进程(现场)进行分析,充分利用了已有的问题进程;4)上述方法作为其他内存泄漏调试方法的一种补充,一种值得尝试的方法,可以作为参考。

百度MTC是业界领先的移动应用测试服务平台,为广大开发者在移动应用测试中面临的成本、技术和效率问题提供解决方案。同时分享行业领先的百度技术,作者来自百度员工和业界领袖等。

>>如有问题,欢迎与我沟通

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1090824.htmlTechArticle利用进程信息追查内存泄漏 摘要:内存泄漏是后台服务器程序经常遇见的软件问题,定位内存泄漏的方法有很多,例如valgrind,但需要重启...


推荐阅读
  • Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理,现在在企业中的使用率也是很广的。git是一个分布式的版本控制系统,不像以前的svn,svn是 ... [详细]
  • docker整体了解
    Docker是一个基于LXC技术构建的容器引擎,基于Go语言开发,遵循Apache2.0协议开源Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移 ... [详细]
  • 1.研究背景及其意义互联网从发展到至今,已经深入到人们的日常生活中,并且不论老人还是小孩,多少都会接触到互联网。在这个越来越信息化的社会& ... [详细]
  • 如何设计一个秒杀系统(各方面都写的很到位)
    1.Overview1.1并发读写秒杀要解决的主要问题是:并发读与并发写。并发读的优化理念是尽量减少用户到服务端来读数据,或者让他 ... [详细]
  • 域名前缀和后缀html,为什么域名前要加www前缀,www是什么意思?立金哥
    为什么域名前要加www前缀?MichaelFLiu号召大家把域名前面的www去掉,我深以为然。好域名都被瓜分光了,大家手里的域名都老长老长 ... [详细]
  • mysql oneproxy稳定吗_Mysql 中间件 oneProxy总结
    建议使用之前把官方的文档全部通读一遍这里提供一个我的网盘地址oneproxy百度网盘0.先对oneproxy有个大概的了解,知道他所处的位置1.MySQL服务器创建t ... [详细]
  • 吴恩达“机器学习”——学习笔记二
    定义一些名词欠拟合(underfitting):数据中的某些成分未被捕获到,比如拟合结果是二次函数,结果才只拟合出了一次函数。过拟合(overfitting):使用过量的特征集合, ... [详细]
  • 云计算安全,主要面临哪些威胁?
    云计算是一种新的计算方式,它依托于互联网,以网络技术、分布式计算为基础,实现按需自服务、快速弹性构建、服务可测量等特点的新一代计算方式。然而,任何以互联网为基础的应用都存在着一定危 ... [详细]
  • selenium 定位方式3css_selector
    关于页面元素定位,可以根据id、class、name属性以及link_text。其中id属性是最理想的定位方式,class与name属性, ... [详细]
  • 根据时间更改网站背景的脚本。热!
    我在网上找到了它,并以自己的方式对其进行了自定义;作者的功劳就在那里。实际上,这是一个用于更改背景颜色的脚本,并且在我看来& ... [详细]
  • 两种方式实现Flink异步IO查询Mysql
    如官网所描述的Flink支持两种方式实现异步IO查询外部系统http ... [详细]
  • 缓冲区溢出实例(一)–Windows
    一、基本概念缓冲区溢出:当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被填满从而覆盖了相邻内存区域的数据。可以修改内存数据,造成进程劫持,执行恶意代码,获 ... [详细]
  • socket8 [命名管道]
    ::命名管道不但能实现同一台机器上两个进程通信,还能在网络中不同机器上的两个进程之间的通信机制。与邮槽不同,命名管道是采用基于连接并且可靠的传输方式,所以命名管道传输数据只能一对一 ... [详细]
  • 1、对于List而言,要不然就使用迭代器,要不然就从后往前删除,从前往后删除会出现角标越界。因为我List有两个remove方法,一个是int作为形参(删除指定位置的元素),一个是 ... [详细]
  • 为什么80%的码农都做不了架构师?#0系列目录#聊聊远程通信Java远程通讯技术及原理分析聊聊Socket、TCPIP、HTTP、FTP及网 ... [详细]
author-avatar
此情为谁伤
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有