珍惜每一滴水(kbmmw 中的内存调试)
作为一个服务器端的应用,最基本的要求就是稳定,当然要做一个稳定的服务器端,需要涉及到很多方面,
内存泄露就是稳定的一个致命杀手,因为服务器的物理内存是有限的,即使一个功能有很小的内存泄露,经过
长时间的运行,也会累积成一个非常大的内存泄露,导致服务器内存耗尽,系统崩溃。因此珍惜服务器资源是
开发者必须重视的(当然了,对于内存无法管理的语言及框架,那就算了)。
最新版的kbmmw 中加入了内存调试功能,这个功能不但可以应用在kbmmw 服务器中,你也可以在其他程序中
使用。
其实自从delphi 2007 开始,delphi 就默认使用FastMM 作为内存管理器,FastMM 也自带了内存泄露报告功能。
但是FastMM只能追踪通过GetMem 分配的内存资源,无法追踪windows其他方式分配的内存(Virtual/Heap/Global/Local).
另外,即使你屏蔽了FastMM 的内存泄露报告,kbmmw 的内存调试器可以在应用中的任何时间来记录内存的使用情况。
我们要使用kbmmw 的内存调试功能,先做一下准备工作。
首先我们要打开 kbmmwconfig.inc 加入
{$DEFINE KBMMW_SUPPORT_DEBUGMEMORY}
{$DEFINE KBMMW_INSTALL_DEBUGMEMORY_HANDLERS}
这两句条件编译,以便激活kbmmw 的内存调试功能,默认是关闭的,如图。
如果没有 {$DEFINE KBMMW_SUPPORT_DEBUGMEMORY} 这一句,内存调试不起作用。
如果没有 {$DEFINE KBMMW_INSTALL_DEBUGMEMORY_HANDLERS},内存追踪不起作用。
现在就可以在你的程序里面 加入kbmMWDebugMemory 单元了,你的程序就有了内存调试功能了。
kbmmw 自动的勾住了 delphi 程序中所有的内存分配功能,包括以前的borlandMM, 和windows 自己的Virtualxxx, Globalxxx,
Localxxx and Heapxxx 这些函数。当然了,也完美支持Fastmm 的内存管理器。
kbmmw 为每次的内存分配和内存再分配都用一个唯一的自增64位 数来标记。通过这个数字,我们可以追踪在某个时间段中内存分配
的情况,这些点叫checkpoints.原理上,一个checkpoint 就是一个64位的数字,通过这些数字我们可以追踪每次的内存分配情况。
kbmmw 同时维护着一个分配的内存及分配次数的统计表。你可以任何时候来查阅这个统计表
procedure TForm1.Timer1Timer(Sender: TObject); begin lLiveAllocationsCount.Caption:=inttostr(TkbmMWDebugMemory.LiveAllocationCount)+' ('+inttostr(TkbmMWDebugMemory.LiveAllocationCountPerSec)+'/sec)'; lLiveAllocSize.Caption:=inttostr(TkbmMWDebugMemory.LiveAllocationSize)+' ('+inttostr(TkbmMWDebugMemory.LiveAllocationSizePerSec)+'/sec)'; lMaxAllocationCount.Caption:=inttostr(TkbmMWDebugMemory.MaxAllocationCount); lMaxAllocationSize.Caption:=inttostr(TkbmMWDebugMemory.MaxAllocationSize); lMaxCapacity.Caption:=inttostr(TkbmMWDebugMemory.CurrentAllocationCountCapacity); lCheckPoint.Caption:=inttostr(FCheckPoint); lGCCount.Caption:=inttostr(TkbmMWDebugMemory.AllocationKeys.GCCount); end;
其中:
LiveAllocationCount 是当然使用的、活动着的内存。它包括所有的 (Borland- object/string/memory, Localmemory, Globalmemory, Virtualmemory, Heapmemory).
其它参数的作用请参考kbmmw 的源代码(没有源代码?那就买一份呗)。
知道了内存分配情况,那么接下来就是如何在程序结束后检测内存泄露了。
什么时候内存泄露?
就是被分配的内存,永远没有被释放。
有些内存泄露无所谓,因为其在整个应用过程中只发生一次,有点像定义了一个全局变量,这种泄漏时是可预测的,而且不随时间的增长而增长,这类泄露在delphi
的RTL 和VCL 中有很多,我们无法消灭它们,也没必要消灭它们(对于爱干、鱼儿的这些强迫症患者简直就是噩梦)。
对于那些每次执行都发生的内存泄露,随着时间的增长越来越多,耗尽系统资源,到最后导致系统崩溃。因此这种泄露我们必须消灭。
因为内存追踪无法了解一个人(我不是指“竹子”)的想法,因此在程序运行过程中,无法确定一块主动分配的内存是否真的需要释放?因此只有在程序退出时,
我们才能知道这些内存确实需要释放,然而并没有释放。所以只有在程序退出时,才能确定内存是否泄漏?
在kbmmw 里面,我们可以在尽可能早的地方把内存检测加入。
TkbmMWDebugMemory.ReportDestination('c:\temp\leaks.txt'); TkbmMWDebugMemory.ReportLeaksOnShutdown:=true; TkbmMWDebugMemory.StartLeakChecking;
我们可以做一个明显的泄漏,测试一下
procedure TForm1.Button8Click(Sender: TObject); var sl:Tstringlist; begin sl:=Tstringlist.Create; end;
运行程序,执行以上代码,然后退出。
就会显示以下提示:
如果我们去看leaks.txt
就会很明显的发现这个问题。
以上信息有时很精确,有时不一定准确,主要依靠你程序编译时的一些设置,尤其是优化代码很厉害的话,会差异很大。
当然了,内存使用情况和调试涉及的东西很多,如果你要进一步深入的话,可以看kbmmw 的源码和例子。
有一点需要说明,以上所有的操作都是有代价的,如果在正式生产环境中使用,尽量不要启用内存调试功能。
只有出现内存泄露时,才建议使用以上功能发现泄露原因,消灭掉泄漏后,立即关闭调试功能。
----------------
本来以为完了,鱼儿和竹子追着问如何知道出现发生泄漏的源代码行号,其实kbmmw 里面完全可以实现。
只是需要在编译时增加相关的调试信息。因为没有足够的调试信息,神仙也不知道是源代码的哪一行发生泄露了。
请如图设置。
同时,我们在程序里面需要增加相应的处理代码
procedure TForm1.FormCreate(Sender: TObject); begin TkbmMWDebugStackTrace.Initialize; TkbmMWDebugMemory.ReportDestination('c:\temp\leaks.txt'); TkbmMWDebugMemory.ReportLeaksOnShutdown:=true; TkbmMWDebugMemory.CollectStacks:=True; TkbmMWDebugMemory.StartLeakChecking; FCheckPoint:=TkbmMWDebugMemory.CheckPoint; end;
这样再运行。
先记住我们故意出泄露的地方。
再看看泄露文件
现在可以消停了吧。