比较隐蔽的内存泄露案例分析

标签: 专项测试 未分类 | 发表时间:2014-11-24 22:45 | 作者:百度质量部
出处:http://qa.baidu.com/blog

和大家分享一个笔者在真实项目中遇到的一个内存泄露真实案例.

  1. 1.       问题背景

真实项目中的一个待测模块,这里简化一下,整体可以看做是:输入—中间处理–输出模式,如下图1-1

图1-1 待测模块整体框图

其中中间处理与模块业务相关,这里我们可以暂时看做黑盒,不必关心。

  1. 2.       问题现象

A. 用valgrind跑程序报警图如下1-2,但是报警所指的地方,研发者坚持认为已经释放,是valgrind误报。

 

图1-2 valgrind报警图

 

B. 长期压力测试,未见内存使用持续上升,cpu使用率未见异常;

C.不定时会无故退出:排除人工误操作,日志无异常,无core文件,socket通信已屏蔽SIGPIPE信号。

【NOTICE】在Linux下写socket的程序的时候,如果尝试发送到一个断掉的连接上,就会让底层抛出一个SIGPIPE信号。这个信号的缺省处理方法是退出进程,大多数时候这都不是我们期望的。因此我们需要安全的屏蔽SIGPIPE。

  1. 3.       追查过程

1 )进一步实验的问题现象

这个问题并未引起研发者的注意,研发认为是测试者误操作导致。在程序第一次退出时候,测试者甚至也自我怀疑,是否是误杀了进程或者启动方式有问题。但是测试者在多台机器中启动了程序,观察到了进一步的现象:

A. 都存在文件无故退出问题

B. 退出时间不定,但是都在读完文件之后

C. 观察所有机器的CPU和内存曲线,发现在退出之前都有一个奇怪的波动,波动图案如下图1-3所示:

图1-3 程序退出之前CPU和内存曲线

2 )由现象想到的…

   分析图1-3的日志文件,找到图中A,B时间点日志文件,分析文件发现:

A时间点:读完文件最后一条记录的时间点;

B时间点:程序退出的时间点。

由此推断

(1)     文件读完后,未进入中间业务处理逻辑,CPU未参与计算,所以CPU idle一下子升高了

(2)     但是文件读完以后,内存持续上升,用完资源,最后在B点退出,CPU idle和内存使用恢复正常。

   3) 从现象看本质

      从以上现象和推论,我们可以大胆的猜想: 程序存在内存泄露!并且在 读文件时候没有泄露,因为长期观察,未见内存持续上涨, 内存泄露发生在文件读完以后

 

  1. 4.       验证猜想

根据猜想翻代码:找到如下代码段,如图1-4和1-5,代码段中我省略了一些与这个bug无关的代码:

图1-4 createTask代码

 

图1-5 主程序死循环调用createTask

【从现象找代码】从代码标注的步骤,一步一步往下读,可以发现:

A.              研发者只释放了读文件成功时候(input!=NULL)申请的input资源(如图1-4标注的第6步);

B.              文件读失败,返回NULL(如图1-4标注的第三步);外围函数未释放资源,CreateTask函数里面也未释放input资源;

【从代码看现象】从上图1-3和1-4的代码可以解释,问题现象及图1-3的曲线含义:

A. 当输入大数据文件,性能测试的时候,内存未见持续上升(因为读文件成功,正常释放内存);

B. 读完文件,readOnce继续读文件执行失败,返回NULL,外面函数检查返回是NULL,不释放input,而creatTask()死循环不断执行,不断申请内存,不断读文件失败,直到用完内存,程序退出。(所以才会有图1-3的CPU和内存曲线)。

 

  1. 5.       如何发现类似问题

 

A. 认真对待每一次valgrind报警。(事实证明,valgrind所指之处,研发者释放了大部分内存,但在特定场景—即本文中分析的文件读完的场景下,异常处理部分,未释放内存。)

B. 认真观察程序 执行期间以及 执行完毕后,CPU曲线是否异常,内存是否有持续上涨趋势。

C. 代码评审过程中,需要特别注意正常和异常分支是否有资源泄露的可能。

 

  1. 6.       总结: 如何避免类似问题

 

A. 从研发者角度,养成良好的编程习惯,可以将内存分配和释放的过程封装到一个类中,即在构造的时候申请内存,析构的时候释放内存,从而保证没有内存泄露;

B.从测试者角度,代码评审的时候,特别注意:以下函数在资源获取和资源释放时候要对称出现,即在作用域开头申请资源,在作用域末尾释放资源。

malloc/new /new[] 《- -》 free/delete/delete[]

  • open/socket/accept/pipe 《- -》  close

fopen/popen 《- -》  fclose

fetch_XXX/get_XXX ßà free_XXX/put_XXX/Release_XXX

*_lock 《- -》  *_unlock

 

相关 [内存泄露 分析] 推荐:

使用MAT分析内存泄露

- - Taobao QA Team
对于大型服务端应用程序来说,有些内存泄露问题很难在测试阶段发现,此时就需要分析JVM Heap Dump文件来找出问题. 随着单机内存越来越大,应用heap也开得越来越大,动辄十几G的Dump也不足为奇了. 要快速分析,快速定位问题就必须有给力的工具帮忙,下面我来介绍下常用内存分析工具. JDK自带的一个工具,是JVM Heap导出的必备工具.

比较隐蔽的内存泄露案例分析

- - 百度质量部 | 软件测试 | 测试技术 | 百度测试|QA
和大家分享一个笔者在真实项目中遇到的一个内存泄露真实案例.. 真实项目中的一个待测模块,这里简化一下,整体可以看做是:输入—中间处理–输出模式,如下图1-1. 图1-1 待测模块整体框图. 其中中间处理与模块业务相关,这里我们可以暂时看做黑盒,不必关心. A. 用valgrind跑程序报警图如下1-2,但是报警所指的地方,研发者坚持认为已经释放,是valgrind误报.

关于类加载器内存泄露的分析

- - ITeye博客
从上个世纪90年代Java诞生之日起,Java的类和资源的加载就一直是个问题. 由于它增加了启动和初始化时间,因此这个问题在Java应用服务器上则尤为明显. 为了缓解这个问题,大家试过了不同的访问,比如说以exploaded方式部署,但这只对简单的应用有效;还有2001年发明的Java热插拔的机制.

Android 性能优化之使用MAT分析内存泄露问题

- - CSDN博客推荐文章
转载请注明本文出自xiaanming的博客( http://blog.csdn.net/xiaanming/article/details/42396507),请尊重他人的辛勤劳动成果,谢谢. 内存泄露就像一个定时炸弹,随时都有可能使我们的应用程序崩溃掉,所以作为一名Android开发人员,还是需要有分析内存泄露的能力,说道这里我们还是要说下什么是内存泄露,内存泄露是指有个引用指向一个不再被使用的对象,导致该对象不会被垃圾回收器回收.

Java常见问题分析(内存溢出、内存泄露、线程阻塞等)

- - Java - 编程语言 - ITeye博客
Java垃圾回收机制(GC) . 堆内存3代分布(年轻代、老年代、持久代) . ML(内存泄露) OOM(内存溢出)问题现象及分析 . IBM DUMP分析工具使用介绍. Java应用CPU、线程问题分析. Java垃圾回收机制(GC). 1.GC机制作用 . 1.1 JVM自动检测和释放不再使用的对象内存 .

关于内存泄露

- - 银河里的星星
valgrind 详细说明  http://www.cnblogs.com/wangkangluo1/archive/2011/07/20/2111273.html. 近期Imgsrc一处内存泄露问题的查找和解决  http://rdc.taobao.com/blog/cs/?p=1651.

ios Instruments 内存泄露

- - ITeye博客
虽然iOS 5.0版本之后加入了ARC机制,由于相互引用关系比较复杂时,内存泄露还是可能存在. 这里讲述在没有ARC的情况下,如何使用Instruments来查找程序中的内存泄露,以及NSZombieEnabled设置的使用. 本文假设你已经比较熟悉Obj-C的内存管理机制. 实验的开发环境:XCode 4.5.2.

C++检查内存泄露

- - CSDN博客推荐文章
说明,我使用的ide是vs2008. 内存泄露的检测一般在debug模式下进行. 2.在需要检查内存泄露的cpp头部加上. 4.然后就可以在输出中看泄露情况了. 举个例子,例子中我用newEx表示的上述宏定义中的new. 输出中显示的内容(debug下运行程序,然后点叉叉关闭程序).   Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD .

Java的内存泄露

- - Java译站
Java有垃圾回收,因此不会出现内存泄露. 尽管Java的确有垃圾回收器来回收那些不用的内存块,但你不要指望它能够点铁成金. GC减轻了开发人员肩上的负担,而原本的那些工作非常容易出错,不过并不是所有内存分配的问题它都能够解决. 更糟糕的是,Java的设计允许它可以欺骗GC,使得它能够保留一些程序已经不再使用的内存.

ThreadLocal的内存泄露

- - zzm
ThreadLocal的目的就是为每一个使用ThreadLocal的线程都提供一个值,让该值和使用它的线程绑定,当然每一个线程都可以独立地改变它绑定的值. 如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序.. 关于的ThreadLocal更多内容,请参考《 ThreadLocal》.