内存泄露检测方法介绍
1、内存泄露
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。
2、内存泄露分类
常发性、偶发性、一次性、隐式性。
3、危害
一般用户是很难察觉到程序发生内存泄露,程序会在退出时释放所有内存。但是服务器是长时间运行,如果申请的内存没有得到及时的释放,总有一天会耗尽系统所有内存。
4、内存泄露检测工具
4.1 Visual Leak Detector:
Visual Leak Detector是一款免费、健壮和开源的Visual C++内存泄露检测系统。它非常容易使用,安装vld-2.5-setup.exe软件后,找到头文件和库文件。
4.1.1 添加包含目录
Property pages->Configuration Properties->C/C++->Additional Include Directories:
C:\Program (X86)\Visual Leak Detector\include(库头文件目录,结合实际安装目录而定)。
4.1.2 添加库目录
Property pages->Configuration Properties->Liker->General->Additional Library Directories:
C:\Program (X86)\Visual Leak Detector\lib\win32(库头lib目录,结合实际安装目录而定)。
4.1.3 添加库文件
Property pages->Configuration Properties->Liker->Input->Additional Dependencies:
Vld.lib。
4.1.4 测试代码
#include "vld.h"
#include <string.h>
#include <tchar.h>
#include "..\DllForLeakDetector\DllForLeakDetector.h"
int _tmain(int argc, _TCHAR* argv[])
{
char *p = new char[100];
return 0;
}
其中,必须包含vld.h文件。在Debug模式下运行程序,输出消息如下:
4.1.5 测试结果
从输出消息中可以看出,有一处内存泄露。Visual Leak Detector只能在Debug模式下生效,只能看到是否存在内存泄露,无法知道在哪个模块哪行代码存在问题。
4.2 Visual C++ BoundsChecker
BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。它通过驻留在Visual C++开发环境内部的自动处理调试程序来加速应用程序的开发,缩短产品发布时间。BoundsChecker对于编程中的错误(大多数是C++中特有的)提供了清晰的详细的分析。它能够检测和诊断出在静态,堆栈内存中的错误以及内存和资源泄漏问题。
安装dpvc110000.part1.rar、dpvc110000.part2.rar和DevPartner License Manager.rar,重启vs,菜单栏会出现DevParter图标。
4.2.1 测试代码
int _tmain(int argc, _TCHAR* argv[])
{
char *p = new char[1024];
}
4.2.2 开启测试
启动DevParter->Start with Error Detection。
4.2.3 测试结果
运行结束后会生产内存检测报告如下,可以定位到内存泄露的具体代码行数。
4.3 windbg检测内存泄露
4.3.1 设置环境变量
变量:_NT_SYMBOL_PATH
变量值:SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols;“测试项目pdb目录“
4.3.2 创建用户态堆栈追踪数据库
使用前UMDH捕捉堆分配的过程中,您必须配置Windows捕获堆栈跟踪。
启用堆栈跟踪捕获过程,使用GFlags设置创建用户模式堆栈跟踪数据库标志的过程。
4.3.3 使用以下Gflags命令
gflags /i ImageName +ust
ImageName是指可执行映像文件的名称,如Notepad.exe。
这个设置会影响所有程序的新实例。它不影响正在运行的程序的实例。
分析正在运行的进程:
使用以下命令来记录和分析堆内存分配在一个运行的进程。这一分析的重点是堆栈跟踪。
语法:
umdh -p:PID [-f:LogFile] [-v[:MsgFile]] | [-g] | [-h]
4.3.4 分析比较日志
你可以生成多个UMDH日志相同的过程。然后,您可以使用UMDH比较日志和确定哪些调用堆栈分配增长之间最试验。
例如,下面的命令将UMDH比较两个UMDH日志,Log1。txt和Log2。三种,并把输出重定向到文件中,三分之一Compare.txt。
umdh –d Log1.txt Log2.txt > Compare.txt
结果比较Compare.txt文件列出了每个日志中记录调用堆栈,每个堆栈,显示堆分配的变化之间的日志文件。
4.3.4 测试案例
4.3.4.1 测试代码
int _tmain(int argc, _TCHAR* argv[])
{
cout<< "TestBC Start..." <<endl;
getchar();
char *p = new char[1024];
cout<< "char *p = new char[1024];" <<endl;
getchar();
cout<< "TestBC End..." <<endl;
}
4.3.4.2 打开控制台:win+r->cmd
cd C:\Program Files <x86>\Debugging Tools for Windows<x86>\
4.3.4.3 创建用户态堆栈追踪数据库
Gflags /i TestBC.exe +ust
4.3.4.4 堆栈追踪
umdh –p:9720 –f:D:\test\log1.txt
//testing…
umdh –p:9720 –f:D:\test\log2.txt
其中两个之前发生内存泄露。即测试代码char *p = new char[1024];
4.3.4.5 对比结果
umdh –d D:test\log1.txt D:\test\Compare1.txt
Compare.txt:
由上可以看出存在1024字节的内存泄露,代码行数也被捕获到。
5 现场案例分析
功能是定时获取主板温度,根据预先设定好的换算公式,设置主板风扇风速。其中获取温度是通过WinRing实现的,控制风扇是通过232串口实现的。整个程序以服务形式提供,部署在一体机上。
问题:现场有人反馈,风扇控制服务部署一段时间后,发现服务所占用内存在一直在升高,初步判断应该是内存泄露了。
查看代码,没有发现明显的内存泄露问题。为了保险起见,分别把获取温度代码和设置风扇速度代码单独抠出来进行测试,均没有发现内存泄露。进一步对程序进行分析,剩下最有可疑的就是日志打印库了,因为每次循环都会打印相应的日志信息。
根据上面的分析,单独新建了一个测试Demo,进行一次日志打印操作。通过Visual C++ BoundsChecker运行,测试代码和检测报告如下:
int _tmain(int argc, _TCHAR* argv[])
{
log_init("XXX");
int iCount = 1;
while(iCount-- > 0)
{
LOG ("iCount:%d", iCount);
Sleep(1000);
}
log_Uinit();
return 0;
}
从测试报告可以看出存在内存泄露的问题,服务器经过长时间运行一定会复现现场内存暴涨的问题。查看日志库版本信息,发现库版本较低,替换成最新的库,再一次进行测试,没有发现内存泄露的问题。如果替换库依然有问题,那就需要共享组同事帮忙查看内存泄露问题了。
在排查内存泄露问题上可以考虑使用排除法,即分模块进行测试,逐渐缩小测试范围。最终锁定内存泄露出处。
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。
内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。所以“内存泄漏”是从操作系统的角度来看的。这里的存储空间并不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那么这块内存就泄漏了。
2、内存泄露分类
常发性、偶发性、一次性、隐式性。
3、危害
一般用户是很难察觉到程序发生内存泄露,程序会在退出时释放所有内存。但是服务器是长时间运行,如果申请的内存没有得到及时的释放,总有一天会耗尽系统所有内存。
4、内存泄露检测工具
4.1 Visual Leak Detector:
Visual Leak Detector是一款免费、健壮和开源的Visual C++内存泄露检测系统。它非常容易使用,安装vld-2.5-setup.exe软件后,找到头文件和库文件。
4.1.1 添加包含目录
Property pages->Configuration Properties->C/C++->Additional Include Directories:
C:\Program (X86)\Visual Leak Detector\include(库头文件目录,结合实际安装目录而定)。
4.1.2 添加库目录
Property pages->Configuration Properties->Liker->General->Additional Library Directories:
C:\Program (X86)\Visual Leak Detector\lib\win32(库头lib目录,结合实际安装目录而定)。
4.1.3 添加库文件
Property pages->Configuration Properties->Liker->Input->Additional Dependencies:
Vld.lib。
4.1.4 测试代码
#include "vld.h"
#include <string.h>
#include <tchar.h>
#include "..\DllForLeakDetector\DllForLeakDetector.h"
int _tmain(int argc, _TCHAR* argv[])
{
char *p = new char[100];
return 0;
}
其中,必须包含vld.h文件。在Debug模式下运行程序,输出消息如下:
4.1.5 测试结果
从输出消息中可以看出,有一处内存泄露。Visual Leak Detector只能在Debug模式下生效,只能看到是否存在内存泄露,无法知道在哪个模块哪行代码存在问题。
4.2 Visual C++ BoundsChecker
BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误。它通过驻留在Visual C++开发环境内部的自动处理调试程序来加速应用程序的开发,缩短产品发布时间。BoundsChecker对于编程中的错误(大多数是C++中特有的)提供了清晰的详细的分析。它能够检测和诊断出在静态,堆栈内存中的错误以及内存和资源泄漏问题。
安装dpvc110000.part1.rar、dpvc110000.part2.rar和DevPartner License Manager.rar,重启vs,菜单栏会出现DevParter图标。
4.2.1 测试代码
int _tmain(int argc, _TCHAR* argv[])
{
char *p = new char[1024];
}
4.2.2 开启测试
启动DevParter->Start with Error Detection。
4.2.3 测试结果
运行结束后会生产内存检测报告如下,可以定位到内存泄露的具体代码行数。
4.3 windbg检测内存泄露
4.3.1 设置环境变量
变量:_NT_SYMBOL_PATH
变量值:SRV*c:\mysymbol* http://msdl.microsoft.com/download/symbols;“测试项目pdb目录“
4.3.2 创建用户态堆栈追踪数据库
使用前UMDH捕捉堆分配的过程中,您必须配置Windows捕获堆栈跟踪。
启用堆栈跟踪捕获过程,使用GFlags设置创建用户模式堆栈跟踪数据库标志的过程。
4.3.3 使用以下Gflags命令
gflags /i ImageName +ust
ImageName是指可执行映像文件的名称,如Notepad.exe。
这个设置会影响所有程序的新实例。它不影响正在运行的程序的实例。
分析正在运行的进程:
使用以下命令来记录和分析堆内存分配在一个运行的进程。这一分析的重点是堆栈跟踪。
语法:
umdh -p:PID [-f:LogFile] [-v[:MsgFile]] | [-g] | [-h]
4.3.4 分析比较日志
你可以生成多个UMDH日志相同的过程。然后,您可以使用UMDH比较日志和确定哪些调用堆栈分配增长之间最试验。
例如,下面的命令将UMDH比较两个UMDH日志,Log1。txt和Log2。三种,并把输出重定向到文件中,三分之一Compare.txt。
umdh –d Log1.txt Log2.txt > Compare.txt
结果比较Compare.txt文件列出了每个日志中记录调用堆栈,每个堆栈,显示堆分配的变化之间的日志文件。
4.3.4 测试案例
4.3.4.1 测试代码
int _tmain(int argc, _TCHAR* argv[])
{
cout<< "TestBC Start..." <<endl;
getchar();
char *p = new char[1024];
cout<< "char *p = new char[1024];" <<endl;
getchar();
cout<< "TestBC End..." <<endl;
}
4.3.4.2 打开控制台:win+r->cmd
cd C:\Program Files <x86>\Debugging Tools for Windows<x86>\
4.3.4.3 创建用户态堆栈追踪数据库
Gflags /i TestBC.exe +ust
4.3.4.4 堆栈追踪
umdh –p:9720 –f:D:\test\log1.txt
//testing…
umdh –p:9720 –f:D:\test\log2.txt
其中两个之前发生内存泄露。即测试代码char *p = new char[1024];
4.3.4.5 对比结果
umdh –d D:test\log1.txt D:\test\Compare1.txt
Compare.txt:
由上可以看出存在1024字节的内存泄露,代码行数也被捕获到。
5 现场案例分析
功能是定时获取主板温度,根据预先设定好的换算公式,设置主板风扇风速。其中获取温度是通过WinRing实现的,控制风扇是通过232串口实现的。整个程序以服务形式提供,部署在一体机上。
问题:现场有人反馈,风扇控制服务部署一段时间后,发现服务所占用内存在一直在升高,初步判断应该是内存泄露了。
查看代码,没有发现明显的内存泄露问题。为了保险起见,分别把获取温度代码和设置风扇速度代码单独抠出来进行测试,均没有发现内存泄露。进一步对程序进行分析,剩下最有可疑的就是日志打印库了,因为每次循环都会打印相应的日志信息。
根据上面的分析,单独新建了一个测试Demo,进行一次日志打印操作。通过Visual C++ BoundsChecker运行,测试代码和检测报告如下:
int _tmain(int argc, _TCHAR* argv[])
{
log_init("XXX");
int iCount = 1;
while(iCount-- > 0)
{
LOG ("iCount:%d", iCount);
Sleep(1000);
}
log_Uinit();
return 0;
}
从测试报告可以看出存在内存泄露的问题,服务器经过长时间运行一定会复现现场内存暴涨的问题。查看日志库版本信息,发现库版本较低,替换成最新的库,再一次进行测试,没有发现内存泄露的问题。如果替换库依然有问题,那就需要共享组同事帮忙查看内存泄露问题了。
在排查内存泄露问题上可以考虑使用排除法,即分模块进行测试,逐渐缩小测试范围。最终锁定内存泄露出处。