红鱼儿

使用kbmMW调试内存泄漏

使用kbmMW调试内存使用情况

kbmMW当前版本包含越来越多的功能,如进行常规日志的记录,审核,记录运行时异常处理的堆栈跟踪功能,现在还具有内存使用的调试功能。这些功能实际上可用于任何应用程序,甚至包括不使用kbmMW其他部分的应用程序。

我已经写了一些有关kbmMW中的日志记录和审计系统的文章,其中还介绍了异常处理时进行堆栈跟踪,最新添加的功能是能够实时跟踪应用程序的每个内存分配。

为什么有了内存泄漏检测功能,例如FastMM提供的,还要用kbmMW带的呢?

FastMM仅跟踪通过常规GetMem等内存分配完成的内存分配。它不会跟踪通过Windows中可用的任何Virtual/Heap/Global/Local分配方法进行的分配。

即使不用FastMM内存泄漏检测,也可以用kbmMW内存调试器在应用程序中随时记录内存使用情况。

起步

首先需要把kbmMWDebugMemory添加到应用程序的uses子句中,其次必须确保在kbmMWConfig.inc文件中设置了以下定义:

{$DEFINE KBMMW_SUPPORT_DEBUGMEMORY}
{$DEFINE KBMMW_INSTALL_DEBUGMEMORY_HANDLERS}

否则,将禁用kbmMW所有的内存调试。

如果省略KBMMW_SUPPORT_DEBUGMEMORY,则没有内存调试功能(包括所有功能/方法)可用。

如果省略KBMMW_INSTALL_DEBUGMEMORY_HANDLERS,则内存调试系统将不会自动安装hooks和处理程序,因此这些功能可能可用(请参见上文),但是不会进行内存跟踪。

设置两个定义后,将kbmMWDebugMemory添加到应用程序或单元的use子句中,将自动安装kbmMW的内存调试功能和hooks。

为了充分利用它,您还应该确保使用调试,堆栈跟踪和外部TDS文件或详细的MAP文件来构建应用程序。如果要在IDE外部进行内存调试,则生成的* .tds或* .map文件在运行时必须与可执行文件位于同一目录中。

概念

kbmMW自动挂接Borland类型的内存分配,Microsoft Windows Virtualxxx,Globalxxx,Localxxx和Heapxxx分配方法。它可以与任何第三方内存管理器(包括FastMM)完美配合。

进行的每个分配和重新分配,均以kbmMW分配唯一的递增64位数字。此数字可用于跟踪在两个时间点(称为检查点)之间进行的所有分配。

基本上,检查点只是一个64位数字。这样,您可以准确检查代码的子集中发生了什么分配,甚至可以跟踪到分配发生的位置。

统计

kbmMW维护有关已分配内存和分配计数的统计信息。这些信息你可以随时获取,例如:

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);

LiveAllocationCount是检测到的活动数,即正在使用的内存分配的数量。它计算所有类型的分配(Borland – object/string/memory, Local memory, Global memory, Virtual memory, Heap memory )。例如,由于FastMM将通常通过VirtualAlloc分配大量内存,并使用GetMem(Borlands内存管理器接口)将该内存分配给应用程序,因此您会发现计数(和大小)不精确,因为计数将包括FastMM制造的VirtualAlloc,以及应用程序进行的单个GetMem(等)调用。

因此,实时值是可比较的值,但不是精确值。您可以依靠它们来显示例如增加的内存使用量(也许表明有泄漏),但是您不能直接依赖于确切的绝对值,因为由于上述情况某些分配被计算两次。

应用关闭时检疫泄漏

什么是泄漏?它是已分配的内存从未释放。

有些泄漏是不好的,有些不是,象单个非重复分配而发生的泄漏,这些分配通常在应用程序启动期间进行,但从未明确释放。实际上,RTL和VCL包含大量此类泄漏。这些泄漏没有什么问题,因为操作系统(在本例中为Windows)将在应用程序关闭时自动释放应用程序使用的所有内存。

严重的泄漏是那些反复分配内存,但从不释放。这些泄漏最终将导致应用程序用尽内存空间,并且/或者由于物理RAM的耗尽(导致分页)而使系统运行极其缓慢。分页是将当前不太重要的内存段写入磁盘的空间,以便为当前正在分配或从磁盘(页面文件)中读取的当前更重要的内存段腾出空间。

在系统上发生某些分页是很正常的,但是如果应用程序分配的内存足以减慢所有其他进程的速度,而这仅仅是由于发生分页,那是不正常的。

发生严重泄漏(重复分配内存)。

只有在应用程序关闭时才能合理可靠地检测是否发生了这些严重的泄漏。为什么?因为那时您知道所有(大部分)分配的内存应该已经由应用程序的析构函数/ FormClose事件等释放。

kbmMW使检查变得简单。在应用程序启动时运行下面几行:

TkbmMWDebugMemory.ReportDestination('c:\temp\leaks.txt');

TkbmMWDebugMemory.ReportLeaksOnShutdown:=true;
TkbmMWDebugMemory.StartLeakChecking;

现在,您已经定义了泄漏报告的存储位置,希望在关机时创建自动泄漏报告,并希望立即启动泄漏检测。实际上,StartLeakChecking所做的全部工作就是加载任何TDS / MAP调试信息(出于堆栈跟踪目的),然后注册一个基线检查点,从该检查点中,它将检查分配是否已释放。在此时间之前的所有分配都将被忽略,因此所有的内置VCL / RTL泄漏以及为TDS / MAP信息分配的所有对象。

您实际上可以通过检查以下内容来查看基准值:

ShowMessage(‘Baseline=’+inttostr(TkbmMWDebugMemory.Baseline));

如果要使泄漏检测包括安装了内存分配hook后的所有内容,请执行以下操作。将基线设置为零。例如。

TkbmMWDebugMemory.Baseline:=0;

可以通过以下方式获取最后分配的检查点:

var
  cp:TkbmMWDebugMemoryAllocationKey;
…
    cp:=TkbmMWDebugMemory.Checkpoint;
    ShowMessage(‘Checkpoint=’+inttostr(key));

现在,当您运行应用程序,然后再次关闭它时,kbmMW将生成一个报告,报告未释放的内存。

默认情况下,它将在屏幕上显示概述状态,如下所示:

如您所见,调试器能够确定其对象,字符串或其他类型的内存是否已泄漏,并在第一部分中为您提供了有关特定类的多少实例泄漏的统计信息。在这种情况下,只有一个TkbmMWInnerThread实例。这是一个安全的泄漏,仅存在是因为kbmMW调度程序有一个宽松的事件线程运行处理事件。内存调试器已注册一个计划事件,用于计算分配/秒。

堆栈跟踪的准确性可能更高或更低,这取决于编译到应用程序中的调试信息的数量(必须启用堆栈框架),以及是否让Delphi生成kbmMW的堆栈跟踪功能可以使外部TDS或MAP文件采用。

您可以选择不收集分配的堆栈跟踪。这将节省一些内存和CPU时间,并使您的报告不那么冗长。这可以通过在StartLeakChecking调用之前添加以下行来完成

TkbmMWDebugMemory.CollectStacks:=false;

在特殊情况下跟踪内存分配

您可能需要报告在特定时间间隔内或代码中两个位置之间所做的所有分配。通过使用检查点方法来获取两个位置的数字,然后在报表中选择所需的内容,即可完成此操作。例如:

var
   FMyCP1,FMyCP2: TkbmMWDebugMemoryAllocationKey;
…
     FMyCP1:=TkbmMWDebugMemory.Checkpoint;
     <your interesting code>
     FMYCP2:= TkbmMWDebugMemory.Checkpoint;
     MyReport(FMyCP1,FMyCP2);

…

procedure MyReport(ACP1,ACP2: TkbmMWDebugMemoryAllocationKey);
var
   sr:TkbmMWDebugMemoryScanResult;
begin
     sr:=TkbmMWDebugMemoryScanResult.Create;
     try
        TkbmMWDebugMemory.Scan(sr,FMyCP1,FMyCP2,[mwdmstObject]);
        sr.Log(mwltDebug,mwllDetailed,[mwsrpoNoStack]);
     finally
        sr.Free;
     end;
end;

上面的示例仅报告对象实例分配,并且将通过kbmMW日志系统将其报告为调试日志,而没有任何堆栈跟踪。

原文地址:https://components4developers.blog/2020/06/09/didyouknow-5-leakdetection/

 

posted on 2020-06-12 13:09  红鱼儿  阅读(589)  评论(0编辑  收藏  举报