珍惜每一滴水(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;
复制代码

 

这样再运行。

先记住我们故意出泄露的地方。

 

再看看泄露文件

 

现在可以消停了吧。

 

posted @ 2019-07-01 23:46  业容天  阅读(60)  评论(0编辑  收藏  举报