技术交流

TECHNICAL EXCHANGES

【技术分享】内存方面的技术问题总结


一、内存问题简述

       内存问题一般有内存泄漏、内存申请失败、内存总量占用过大等问题。

       内存泄漏是非常严重的内存问题,一般是一定要解决的,也是先要去关注的问题,这个问题导致的结果有,一个是内存申请失败导致程序崩溃,一个是出现系统内存不足的错误,还有一种就是比较介于中间的结果,系统的反映时间明显降低,资源耗尽等。


       内存申请失败的错误排除掉硬件问题,一般有以下几种情况导致: 

1、 应用程序虚拟内存不足,导致申请失败。类似上述的内存总量过大的问题。 

1、 内存碎片太多。 即进程使用的堆被碎片化,再去申请大一些的空间便会失败。

2、 一次性申请空间太大。这个问题可以通过编程规范来约束,原因类似内存碎片问题。


用到的内存数据查看软件

任务管理器Taskmgr.exe可方便地发现进程的内存使用情况,并可检查在一段时间内一些简单的进程指标的趋势。使用任务管理器查看物理内存及虚拟内存的使用情况(WIN7下为专用工作集大小及提交内存大小)。

VMMAP工具:会显示专有内存及提交内存等。



二、内存泄漏问题的检查方法

      内存泄漏的发生方式

&  常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

&  偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

&  一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块且仅有一块内存发生泄漏。

&  隐性的内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存,又或者某一个容易每帧都会Push,但是却从来没有pop或者clear,这样会造成一些死数据。严格的说这里并没有发生内存泄漏,因为最终程序会释放掉所有申请的内存。但是,不及时释放内存也可能导致最终耗尽系统的所有内存。

定位泄漏在具体位置

       定位到某个模块或者某个功能点有内存泄漏最直接的方式是通过一些测试。如果这个功能点比较大,最好可以分开列出一些单项的测试进行具体定位。

       通过某些第三方的内存检测工具,也有助于定位问题。例如GLOWCODE、WINDBG等。

       通过第三方工具定位到问题后,就需要查找问题出现的原因。对于比较明显的泄漏,可以通过查看代码轻松解决。

       对于非隐性的内存泄漏,也可以通过MMGR来具体定位是哪个对象New之后没有Delete(也可以一开始就启用MMGR),一般的问题要么是分配完内存后忘记回收,要么代码有问题,造成想回收却无法回收,再或者某些API函数操作不正确,造成了泄漏等。

       使用MMGR需要注意最好在程序退出时释放所有的全局对象,单件对象等,且注意释放顺序,否则MMGR会报出一大堆这方面相关的错误。更多的时候,我们是写一些单项的测试用例,将整个问题单项化,尽量缩小范围,然后通过打日志或者通过第三方工具定位具体的涨幅堆栈,进而找到内存问题。


Glowcode内存检测工具

1、 模块级别的查看比较

通过GLOWCODE的CALL Summary功能,可以查看进程各模块的内存使用情况(也可以看到某些比较大的函数使用的内存,模块需要指定HOOK上)。这样,我们就可以通过比较两个时间戳的Summary内存的增长在某个具体的模块或某个具体的函数。


2、 堆栈级别的查看比较

在Native Memory功能,GLOWCODE可以帮忙检测出一些比较简单的泄漏问题。这个功能还会提供StackTrace,并收集每个Stack的内存分配情况。这样就可以根据比较两次StackTrace的情况来定位具体是哪一个Stack导致的内存增长问题。


3、 通过GLOWCODE观察进程使用的整体堆内存分布:


WINDBG工具使用

WinDbg是微软开发的免费代码级别调试工具,其可以用于Kernel模式调试和用户模式调试,还可以调试Dump文件。对于调试内存方面,WinDbg提供了一种用!heap扩展命令,可以在任意时刻分析内存的Heap使用情况。通过两次Heap使用情况的比较,也可以定位一些内存问题。


程序日志打印的方法:

可以将两次程序中使用的资源管理器对象管理器数据的数量及信息打印出来,然后做两次对比,也能帮助定位问题。

三、内存碎片的检查方法

内存碎片的定位

       前面提到,使用VirtualQuery可以打印出当前进程使用的虚拟地址空间的情况。通过分析DUMP出来的数据,基本就可以定位到进程的内存碎片情况。

       通过多次DUMP碎片情况,可以分析程序的碎片化走势。


已知的系统内存管理的特点

        一般应用程序都是通过New操作来进行内存申请,其使用的是CRT的内存管理机制。在WINXP下,CRT提供的DELETE对于程序Commit出来的某些内存,不会真正的Free掉,而是将次内存Reserve住。

       WIN7下的内存管理有一种机制是LFH (Low Fragmentation Heap),LFH是应用程序可以采用的一种内存使用的策略。WIN7的内存管理器会比XP下好很多。


内存碎片的解决

n  使用对象池

使用对象池可以从一定程度上减少New操作的发生。

n  使用内存池,这可以算是一种根本性的解决方式。引擎中使用了nedmalloc的内存池管理,此管理器会直接使用Virtual MemoryAPI申请内存数据,并交给应用程序使用。程序退出时统一VirtualFree掉。


四、程序设计方面的注意事项

尽量不要跨DLL使用STL的接口

       跨DLL使用STL的方式很危险,且有诸多限制,例如要求模块中使用的STL版本一致,且STL中使用的内存分配器也一致、两个工程需要有相同的编译选项,且应当避免使用含有实现中含有静态数据段的STL容器,以防止内存访问违规。

另外,建议封装STL各类型容器,在具体使用时使用自定义的STL扩展形式,便于以后轻松替换各种Allocator。

尽量不要跨DLL进行内存的申请和释放

       对于自己写的DLL,如果出现一个DLL中New了一个对象,然后在另一个自己写的DLL中释放,除了要保证两个模块编译选项一致外,还要保证底层的New和Delete所用到的内存分配器库统一,否则便会很容易出现内存错误了。


开发阶段尽量去掉对象池的使用

      对象池的使用有一定容错功能,开发阶段去掉对象池,可以将某些错误暴漏出来。

Copyright © 2005-2016 Yonghang Technology. All Rights Reserved   
京ICP备09024877号 京公网安备11010102000822号.