当前位置:   article > 正文

内存泄漏问题分析思路(案例篇)_排查内存泄露的思路 过程和手段

排查内存泄露的思路 过程和手段

在这里插入图片描述
今天向大家分享一个工作中,内存泄漏问题的解决流程及其思路。相信对刚接触这一块的朋友,有借鉴意义。

1. 背景介绍

最近XX项目中,XX厂商反馈我们的XX程序在指定情况下,会产生内存泄漏,随着时间的增长,造成OOM错误。该问题本由AA同事处理,但由于我比较感兴趣,就一同分析并最终解决。虽然最终的解决方式比较简单,但是问题分析的流程,我觉得还是比较具有参考意义的。在这里向大家分享这个内存泄漏问题从出现到解决的流程。

1.1 问题出现

XX厂商同事反馈:当APA与车机的连接线断开时,我们的XX程序会随着运行时间的延长,其堆空间占用会越来越大。最终导致OOM错误。

判断堆空间增加的方式为:cat /proc/[pid]/smaps

1.2 头脑风暴

当我从AA同事这里得到上述的信息时,我脑海中,已经冒出了两个问题:

APA与车机的连接线断开,这是问题出现的条件。根据XX程序的代码逻辑,若有内存泄漏,应该是不断尝试ups_init导致的。(确定了内存泄漏代码方向)
cat /proc/[pid]/smaps,这是XX厂商判断内存泄漏的依据。因为,我并不了解smaps文件属性,所以我第一反应是持有怀疑态度的(他们的这个判断依据可能不正确)。
根据上面两个问题,就决定了我接下来的分析思路:

了解smaps 文件属性,该判断依据是否可靠。
使用valgrind 工具判断uc-slave的内存泄漏点。(其实直接走这一步就可以,主要我也想学习一下smaps文件属性)

2. 分析

在分析smaps 文件属性之前,我先向大家介绍一下两个概念:虚拟内存,驻留内存。

关于这两个概念,我借用一个大佬的图分享一下。

在这里插入图片描述

图一:虚拟内存空间到物理内存空间的映射

虚拟内存:就是假象的内存空间,程序中变量,函数的地址,其实都是虚拟内存空间的地址。但是程序运行时,会将虚拟内存中需要被访问的部分映射到物理内存中(暂时不会用到的应该在磁盘)。

问题:请问暂时用不到的代码段,数据段,存储在哪呢?若是在磁盘中,linux 系统中当程序运行后,再将可执行文件删除,为什么不影响呢?

答案我就不在这描述,希望大家可以去自己研究一下,有答案的朋友可以在评论区进行回答。

驻留内存:就是那些被映射到进程虚拟内存空间的物理内存。

图一中,物理内存中被着色的就是驻留内存。比如A1,A2,A3,A4,是A进程的驻留内存;B1,B2,B3,是B进程的驻留内存。我们一般所说的进程占用多少内存,就是指进程的驻留内存。

问题:其中A4与B3在一个物理内存中,表示两个进程的虚拟内存映射到同一块物理内存,这种情况合理吗?

答案是合理的,这种情况称为共享内存,表示这块内存是可以被多个进程访问的。比如,我们可以采用共享内存的方式,进行多进程通信;现在大部分的进程也会依赖一些动态库,比如libc.so,libld.so等。这些库仅会在物理内存中保留一份,达到节省内存的目的。这也是动态库相比较于静态库的优势。

2.1 smaps 文件属性

smaps文件是描述了进程的内存消耗情况。文件内容格式如下:
在这里插入图片描述
图二:smaps 文件格式

简单描述文件中各字段的属性:

其中第一行从左到右依次表示:虚拟空间的地址范围 权限表示 映射文件偏移 设备号 inode 文件路径
Size: 表示该虚拟空间的范围大小
Rss:表示该映射区域当前在物理内存中占用的空间
Pss:表示共享内存中平摊的物理内存。
Shared_Clean:和其它进程共享的未修改的page的大小
Shared_Dirty:和其它进程共享的被改写的page的大小
Private_Clean:未被改写的私有页面的大小
Private_Dirty:已被改写的私有页面的大小
而客户反应的问题是:随着程序运行时间增长,堆空间占用的内存会越来越大。如下图:
在这里插入图片描述
图二:客户反馈堆空间一直增加

综上所述,我认为客户的判断依据是正确的,并且是非常好的方式

2.2 valgrind 工具

既然已经确认存在内存泄漏问题,那么接下来就是解决内存泄漏了。关于定位内存泄漏问题,我们肯定会想到valgrind工具。很可惜,客户平台上并没有valgrind 工具,那么就需要我们进行工具移植了。开源工具的移植可参考我的另一篇文章《开源工具移植(gdb)》。

而valgrind的使用方式,可参考我的博客《快速定位内存泄漏的套路》。

一个小时过去了~~~

通过上面的移植及调试步骤,得到了下面的两个log信息文件。

在这里插入图片描述
图三:valgrind 信息对比

左图是程序启动后,内存消耗情况,有164个内存消耗记录;右图是程序运行半小时,内存消耗情况,有204个内存消耗记录。

从图中可知,随着运行时间增加的内存消耗,主要集中在still reachable中(表示这部分的内存还是有机会被释放的,说明指向该部分内存的地址是有保存的,猜测是代码中没有释放)。

再详细对比两个文件中内存消耗的记录,发现多余的40条记录。都是由MQTTAsync_createWithOptions接口导致的,因此,基本可以确认是由该接口进行malloc申请内存的。

2.3 代码分析

MQTTAsync_createWithOptions接口申请内存接口如下:
在这里插入图片描述
图四: 代码分析

分析:

  1. 该接口中申请内存的变量分别为m,m->c,他们最终都会被分别添加到handlesbstate->clients全局变量中(生产)。正常逻辑有某一个接口对这两个全局变量进行free操作(消费)。
  2. 全局搜索handles调用处,发现有一个接口为MQTTAsync_terminate(局部接口,不会让用户调用),从接口名可以看出,它的作用是消除作用,很符合我们消费操作的接口命名。发现它上层对外接口为MQTTAsync_destroy
  3. 查阅资料MQTTAsync_destroy,你会发现该接口是与MQTTAsync_create(底层调用的就是MQTTAsync_createWithOptions)成对使用的。但是我们代码中并没有对MQTTAsync_destroy进行调用,说明其内存泄漏的原因,是我们编码问题。
  4. 最终解决方式:当MQTT连接失败后,我们即调用MQTTAsync_destroy删除client 句柄。
    在这里插入图片描述
    图五:解决方式

5. 总结:

本篇结合工作实例,从问题出现的背景,思路,分析流程进行描述。其中涉及的知识点也较多。对解决内存泄漏相关问题具有较高的参考意义。
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/176428
推荐阅读
相关标签
  

闽ICP备14008679号