检测Windows程序的内存和资源泄漏之原生语言环境
最近接连收到大客户的反馈,我们开发的一个软件,姑且称之为App-E吧,在项目规模特别大的情况下,长时间使用会逐渐耗尽内存,运行越来越缓慢,软件最终崩溃。由于App-E是使用混合语言开发的,主界面使用C#, 核心模块库大多是采用C/C++实现的DLL,很多配置和用户流程又是用Tcl/Tk脚本语言写的, 普通的内存检测工具比如Purify,Purify Plus, Valgrind 要么不支持Windows,要么不支持混合语言,对于这种复杂架构下的内存问题都无能为力。无奈之下,只好采取分而治之的方式,对不同的代码模块采用不同的检测分析方法。我打算在这篇博客里把分析原生语言内存和资源泄漏过程中尝试过的工具和使用经验做一下初步的总结,下篇文章打算翻译一篇MSDN上关于检测和避免.Net环境下的内存和资源泄漏的佳作。
原生语言的内存泄漏检测
使用微软debug版本的C Run-Time库自带的内存泄漏检测APIs
Microsoft的这篇开发文档:Memory Detection Enabling详细介绍了如何开启内存检测。理论上这种方法很简单,只需要在程序头文件中包含以下三行代码,并在Debug模式下重新编译代码,链接debug版本的C Run-Time 库即可。
#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
#include<crtdbg.h>
但我们的产品恰恰很难满足这两个基本条件,原因是:把产品App-E切换到debug版本的C Run-Time库很费劲,频繁做全编译的代价了也太大了。 很多产品即使在debug模式下也习惯使用release版本的运行库以提高运行速度,我们的产品长久以来就是如此。我试着在Makefile中把Run-Time库从msvcrt.dll切换为msvcprtd.dll,手工编译了几个小模块发现没有问题,include上述头文件也没有问题;但是启动全编译后第二天早晨检查编译结果,发现产生了好几百个编译错误,要知道产品App-E全编译一遍需要8个小时左右,反反复复调试编译问题我根本耗不起。
使用免费的第三方内存泄漏检测库Visual Leak Detector
- 优点
类似于微软的方案,VLD也要求在待分析程序的源代码中包含VLD的头文件并重新编译程序;相对于微软的方案,VLD的最大优点是不需要依赖debug版本的Run-Time库,并且支持C和C++语言,还可以更方便地定制内存检测报告。 - 缺点
即使只想检测某个模块库中的问题,也不能仅仅只在特定库对应的源代码中包括VLD头文件,VLD要求必须在主程序入口处编译链接VLD头文件。这意味着我还是不能避免全编译。此外,VLD也不支持混合代码模式,如果主程序是用C#等托管语言开发的,即使算法库或功能模块库使用native语言开发,VLD也无能为力,我们的产品App-E正是如此。
开发一段简单的内存泄漏检测代码,按需编译
经过几天的分析对比,我已经很确定内存泄漏问题只存在于特定的几个算法库和他们对应的用户界面,那么怎么才能短平快地针对这几个特定的库做内存泄漏分析呢? 内存泄漏检测的机制很简单,自己写段代码实现检测方法,在特定库中编译链接使用岂不是更方便?事实上早就有人这么做了,我不需要重新发明轮子。codeproject有人分享了一段C语言的内存检测代码,基本思想如下:
- 使用宏定义把malloc/free方法映射到自己定义的my_malloc/my_free方法上
- 在my_malloc方法中调用malloc方法分配内存,在一个哈希表中记录内存指针,分配的内存大小和代码调用文件和行数等信息;
C语言的库函数对集合的支持很差,没有直接可用的哈希表,可以自己实现一个链表代替。
主流的C语言编译器都支持__FILE__/__LINE__宏定义用以获取代码调用文件和行数,我们只需要定义一个合适的struct来保存内存分配信息就行; - 在my_free方法中调用free方法释放内存,清除对应的内存分配记录。
- 实现一个report方法,遍历记录内存分配的链表,输出没被释放的内存块对应的代码调用行数等信息。
- 在待分析的模块库代码的合适位置调用report方法,重新编译该模块库。
资源泄漏检测
使用上述codeproject的内存检测代码,对几个特定算法库进行了检测,我发现它们的内存泄漏并不严重,那么为什么程序消耗的内存一直在快速增长,运行速度也越来越慢呢? 考虑到算法模块的用户交互界面是用tcl/tk实现的,我怀疑也许tcl/tk脚本存在诸如GDI对象之类的系统资源泄漏,那有哪些工具可以分析检测资源泄漏呢?
Process Explorer
作为SysInternals工具集的一员,Process Explorer的进程检测和管理能力堪称强大,我平时一直用它替代Windows 任务管理器,最近才发现Process Explorer的Performance Tab页堪称检测进程内存和资源使用情况的利器。
Bear
Bear是一款专注于系统资源检测的Windows小工具,它可以检测:
- 所有GDI对象的使用情况 (hDC, hRegion, hBitmap, hPalette, hFont, hBrush)
- 所以用户对象的使用情况 (hWnd, hMenu, hCursor, SetWindowsHookEx, SetTimer and some other stuff)
- 句柄(Handle)数量
相对于Process Explorer的大而全,Bear专注而细腻,更便于我们监测定位进程具体的资源泄漏类型,从而缩小排查的范围。
结合使用两种工具,对比在进行特定软件操作前后的系统资源变化情况,并对照相应的源代码,我在tk脚本中成功发现了一处资源泄漏,MDI Window上显示的canvas在销毁窗口后没有被destroy,导致在打开关闭含有大量器件的原理图时,每次都会产生2M左右的内存增长,GDI对象更是数以百计地增长。
检测Windows程序的内存和资源泄漏之原生语言环境的更多相关文章
- Java程序在内存中运行详解
目录 Java程序在内存中运行详解 一.JVM的内存分布 二.程序执行的过程 三.只有一个对象时的内存图 四.两个对象使用同一个方法的内存图 五.两个引用指向同一个对象的内存图 六.使用对象类型作为方 ...
- Windows系统中内存泄露与检测工具及方法
1.检测需要使用的工具:windbg工具.检测前,需要先安装windbg工具.安装了该工具后,会在安装目录下有一个umdh工具.假设windbg安装在以下目录下:D:\Program Files\De ...
- eclipse 检测App的内存占用和泄漏【转载】
前段时间开发的Android应用,每次都是在运行了半个小时左右后突然挂掉了,很是莫名其妙,也不知道哪里出了问题,后来一步步排查,发现问题出在JNI层,一个被频繁调用的函数分配的内存忘记释放,导致内存泄 ...
- 使用_CRTDBG_LEAK_CHECK_DF检查VC程序的内存泄漏(转)
我们知道,MFC程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏.例如: class CMyApp : public CWinApp{public:BOOL InitApplicat ...
- [Swift通天遁地]七、数据与安全-(11)如何检测应用程序中的内存泄露
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...
- 编写高质量代码改善C#程序的157个建议——建议69:应使用finally避免资源泄漏
建议69:应使用finally避免资源泄漏 除非发生让应用程序中断的异常,否则finally总是会先于return执行.finally的这个语言特性决定了资源释放的最佳位置就是在finally块中:另 ...
- Windows 程序启动性能优化(先载入EXE,后载入DLL,只取有限的代码载入内存,将CPU的IP指向程序的入口点)
一.重定位链接时重定位:目标文件一般由多个节组成,编译器在编译每个目标文件时一般都是从0地址开始生成代码.当多个代码节合成一个代码段时,需要根据其在最终代码段中的位置做出调整.同时,链接器需要对已经解 ...
- java\c程序的内存分配
JAVA 文件编译执行与虚拟机(JVM)介绍 Java 虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该 ...
- java程序的内存分配
java程序的内存分配 JAVA 文件编译执行与虚拟机(JVM)介绍 Java 虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的 ...
随机推荐
- BOM之history对象
前面的话 history对象保存着用户上网的历史记录,从窗口被打开的那一刻算起.由于安全方面的考虑,开发人员无法得到用户浏览器的URL,但借由用户访问过的页面列表,可以在不知道实际URL的情况下实现后 ...
- 安装软件(基于redhat、centos发行版)
yum 命令的使用: yum local install package_name.rpm 安装本地rpm包yum list updates 列出所有可以更新的安装包yum update packag ...
- 读书笔记 effective c++ Item 12 拷贝对象的所有部分
1.默认构造函数介绍 在设计良好的面向对象系统中,会将对象的内部进行封装,只有两个函数可以拷贝对象:这两个函数分别叫做拷贝构造函数和拷贝赋值运算符.我们把这两个函数统一叫做拷贝函数.从Item5中,我 ...
- DataTable && SqlDataReader帮助理解小程序
// 2015/07/08 using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
- toupper函数及一些小程序
toupper 原型:extern int toupper(int c); 用法:#include <ctype.h> 功能:将字符c转换为大写英文字母 说明:如果c为小写英文字母,则返回 ...
- extjs 基础部分
创建对象的方法: 使用new 关键字创建对象. new classname ([config]) 使用Ext.create方法创建. Ext.create(classname,[config]) n ...
- groovy学习(五) 命令行输入输出
isr = new InputStreamReader(System.in);br = new BufferedReader(isr);name = br.readLine();println(&qu ...
- 深入了解Unity中LineRenderer与TrailRenderer
LineRender和TrailRender是两个好东西,很多Unity拖尾特效都会使用到它们.一些简单的介绍可以参见官方的API文档.在这里探讨一下它们具体的渲染方式,而后给出一些Shader以便更 ...
- 游戏UI框架设计(三) : 窗体的层级管理
游戏UI框架设计(三) ---窗体的层级管理 UI框架中UI窗体的"层级管理",最核心的问题是如何进行窗体的显示管理.窗体(预设)的显示我们前面定义了三种类型: 普通.隐藏其他.反 ...
- zabbix安装详解
关于zabbix及相关服务软件版本: Linux:centos 6.6 nginx:1.9.15 MySQL:5.5.49 PHP:5.5.35 一.安装nginx: 安装依赖包: yum -y in ...