本文来自http://blog.csdn.net/lijun84 ,引用必须注明出处!
在谈及内存泄漏时,对于没有太多经验的新人来说总是很头疼的一件事。因为如果项目早期没有将其纳入代码框架,后期部署上线之后,仅从进程 crash 的 dump 很难找到线索,即使有最后的调用栈信息也很难下手。
本文只想谈谈在各种情况下如何检测内存泄漏,也算给自己这方面的经验做个小结。
貌似谈内存泄漏检测目的有些多余。无非就是定位到泄漏点,然后是失误就修改,是自己的内存管理框架问题,那么就考虑改机制。
但仔细想想什么地方才可能是泄漏点,其实这个问题依情况而定还是比较复杂的。因为造成泄漏无非就是分配了但没有释放,可是你不了解软件的架构或结构,你如何知道正确的处理流程是该什么时候释放,在哪释放?
所以通常内存检测会分为 2 个步骤,第一个步骤通常可以工具辅助自动化,而第二个步骤也许必须人工做。
(1) 检测到大量分配而未释放以造成内存大量消耗或耗尽的代码点
(2) 根据代码结构和实现方式发现应该在哪释放,如何释放 (有可能需要改变代码结构)
内存泄漏的分类:
(1) 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。
(2) 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。
(3) 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。
(4) 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。
内存泄漏的检测形式和阶段:
(1) 架构期纳入:这种情况一般会有自己的内存管理模块,并且有各模块分配 / 释放的追踪机制,且在日志中体现。
优点:
(a) 使内存管理的问题清晰化,更易掌控和全盘布局。
(b) 能尽早发现内存问题。
缺点:
(a) 当项目时间比较紧时,需要花费额外 efforts 。
(b) 追踪机制的代码本身编写要相当谨慎,不能发现泄漏问题。
(2) 项目提交前:此阶段可以用一些侵入式检测工具,这类工具代价也比较小,一般加个头文件或加个编译选项什么的就搞定。
优点:代价小,不会影响项目进度,且通常情况下比较有效。
缺点:在些特定情况下不那么有效(如:项目中做了自己的内存管理框架并有一定程度的封装)
(3) 项目提交并部署后的运行期:这个阶段基本就是亡羊补牢了。也是最头痛的,救火阶段。此阶段一般限制很多(如:无法替换客户程序并且还原客户部署环境,更甚者连客户端的相应版本代码已没了),那么这时基本只能寄希望于一些动态非侵入式的检测工具了。
优点:很幸运,你可能有有效的检测工具可用。
缺点:很不幸,可能此类工具限制比较多,甚至无法使用(多数情况如此,因为现在的软件环境已经很庞大很复杂了)。
内存泄漏检测方法分类:
(1) 替换接口函数法:
这种方法的思想比较简单,并且大量侵入式的检测工具也是这样做的(其实一些 CRT 都有自带,还有些自带的 malloc 调试库)。替换或重载你的 malloc/new 和 free/delete 函数,并做下面几件事:
(a) 在你成功申请内存后:记录你的当前函数名和代码行和你申请的内存块的地址。
(b) 将以此函数名(模块)下的内存块计数器加 1.
(c) 在申请释放内存前:对比所有函数名(模块)下的已分配内存块地址,找到就将相应计数器减 1.
当然实现思想大致如此,可真正实现是需要一些优化的,比如将函数名或内存地址做 hash ,以便省去搜索消耗。
(2) 内存快照对比法:
这种方法不常用,并且效果不太好,但对于以最快的方式发现内存在什么阶段存在泄漏还是比较有效的。
(3) 运行期 Hook 法:
这种方法在很多商业工具中常用,单地说,当你的程序开始运行时,它的 DLL 被自动载入进程的地址空间(这可以通过 system-level 的 Hook 实现),然后它会修改进程中对内存分配和释放的函数调用,让这些调用首先转入它的代码,然后再执行原来的代码。实际只是方法 1 的变种。
(4) 进程资源监控法:
此方法用于取代方法 2 快速判断是否存在内存泄漏问题时比较有效,可以通过系统自带的 Performance Monitor ,查看内存相关计数的值来快速判断是否存在泄漏。
内存泄漏检测工具:
(1) 静态扫描工具: splint, BEAM 等,此类工具可以检测没初始化的变量,废弃的空指针,内存泄漏,冗余计算等潜在问题。但缺点是误报太多。就和编译器的警告一样,你可以重视,但它不一定是引发问题的根源。
(2) 动态侵入性工具: mtrace , dmalloc , memwatch , VLD 等 N 多,都是利用堆栈快照 + TAG 的方法。
(3) 动态非侵入性工具: BoundsChecker , purify, valgrind, YAMD 等,一般用 Hook 运行库的方法。
(4) 资源监控工具: windows 上一般就是 Performance Monitor, linux 可以用 ps 带相应的参数。
小结:
虽然上面介绍了那么多种方法,其实内存泄漏发生时往往现场情况更棘手,资源和信息的缺乏还是给解决问题带来了很大难度。
我个人建议如果团队都是新手,没什么和内存打交道经验的话,尽量避免用 C/C++ ,而 . NET 或 Java 也许成本会低很多,维护代价也会小很多。
当然如果种种原因(如客户要求)不得不用,那么最好能考虑用些成熟的 GC 库,如 HP 的 Boehm’s GC.
最后如果客户连第三方库的使用也限制了,那么你在项目早期就要重视内存管理和追踪的问题。
嘿嘿,如果以上都失效了。也到了最极端的地步,没办法,考验你实力的时候到了,只有 Crack 级调试的经验可以帮助你了。