Delphi: 如何查找和修复EOutOfMemory错误?

7

我正在构建一个Delphi应用程序,用于进行科学模拟。它正在变得越来越复杂,现在包含许多单元和窗体。

每次运行时,我开始出现EOutOFMemory错误。似乎是在函数内部临时使用Variant数组期间或之后发生的。冒昧问一个非常愚蠢的问题,"Variant数组"是否会引起麻烦?(我可以将所有内容转换为字符串,但原则上"Variant数组"可以节省很多麻烦)。

可能存在问题的数组使用如下:

 Function  TProject.GetCurrentProjParamsAsArray(LProjectName, LProjectType : ShortString): ArrayOfVariant;
Var
  ArrayIndex : Word;
begin
    SetLength (Result,54);
    ArrayIndex := 0;
    Result [ArrayIndex] := LProjectName;        Inc(ArrayIndex);
    Result [ArrayIndex] := LProjectType;        Inc(ArrayIndex);                   // this structure makes it easier to add extra fields in the DB than hard coding the array index!!
    Result [ArrayIndex] := FileTool.DateTimeForFileNames    ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  SiteName            ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  PostCode            ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  MetFileNamePath     ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  SiteLat             ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  SiteLong            ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  SiteAlt             ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  TZoneIndex          ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  TZoneHours          ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  TZoneMeridian       ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  Albedo              ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  ArrayTilt           ;    Inc(ArrayIndex);
    Result [ArrayIndex] := SiteAndMet.  ArrayAzimuth        ;    Inc(ArrayIndex);

在任务管理器中-峰值内存使用为42MB,虚拟内存为31M,每次运行时我得到约90,000个页面错误。(在一台带有3GB RAM的XP机器上)
是否有人有关于监控应用程序中不同组件的内存使用情况的通用提示?或者对追踪此错误原因有什么建议?
最近,我已经从将主项目数据存储为CSV格式更改为使用ADO DB,同时我也开始使用Variant数据类型而不是一直在字符串和单/双精度之间进行转换。
我已经遵循了各种可以找到的节省内存的提示,例如,在实际可行的情况下,我已经从.dpr中删除了Application.CreateForm(TProject, Project)语句并动态创建它们(除了表单一直在使用的情况下)。通常我使用最小的实用数据类型(byte、shortstring等)并尽量减少使用'public'变量和函数。
任何提示都非常欢迎,Brian

你预计会出现页面错误。不要担心。 “变量数组”听起来还不错。毕竟,你的数组中只有54个元素。 42MB微不足道。仅凭这些信息几乎不可能说出正在发生什么。 - David Heffernan
ArrayOfVariant是如何定义的?你是在数组中存储对象还是仅存储简单类型? - RRUZ
ArrayOfVariant 在一个单独的实用程序单元中定义(因为你不能在函数之间传递“Array Of....”。即 ArrayOfVariant = Variant 数组)。它仅包含 Double 和 String 值。 - Hamish_Fernsby
1
真正的问题不是这个函数返回后结果会发生什么,而是你是否清理了在此处分配的内存?因为它们似乎是地理点或站点?有多少个这样的数组被创建了? - Despatcher
只是为了确认一下:您的程序是否会出现EOM错误,还是Delphi IDE会出现?例如,D2007本身就因为经常出现EOM错误而臭名昭著。 - Jan Doggen
您需要按照“调试”部分中的描述设置项目选项,以便能够调试程序。http://www.dnabaser.com/learn%20Delphi%20programming%20language/ - Gabriel
6个回答

12

EOutOfMemory在内存管理器无法为给定的分配请求找到连续的内存块时发生。因此,要么是1)分配的内存比预期的多,2)泄漏了已成功分配的内存,3)碎片化(不一定泄漏)内存,因此内存管理器必须随着时间的推移不断分配更多的内存。

当异常发生时,请查看调用堆栈。这将引导您找到无法分配内存的代码。要获取调用堆栈,请在调试器中运行应用程序,或使用像MadExcept、EurekaLog、JCLExcept等异常记录框架。


3
分段是一个合理的解释。但是知道哪个分配失败很少有助于解释内存如何变得分散。 - David Heffernan
通常情况下,如果崩溃的代码与分段的代码相同,则可能会发生这种情况。 - Remy Lebeau
啊,异常日志记录框架听起来非常方便,谢谢,我会尝试的! - Hamish_Fernsby
如果他只想在本地调试程序,那么他就不需要使用MadExcept(和其他类似工具)。他只需要正确设置项目以便进行调试即可。 - Gabriel

3
我怀疑你所展示的代码并不是问题的源头,而只是一种可能出现症状的地方。 如果你怀疑存在一些基本的低级别损坏,你可以尝试打开FastMM的完整调试模式。例如,像你遇到的这种问题可能是由于内存堆的一般性损坏而不是实际上耗尽了内存。
如果你没有堆损坏,而是真正的内存不足问题,则通常需要使用适当的工具,称为分析工具,例如AQTime来查找和解决问题。也许你的分配代码在某种方式上有误,如果你简单地调试代码并发现在某个地方尝试抓取过多的内存,无论是在一个函数中,还是在一系列调用内存分配功能中,这种错误就会变得更加容易理解。
但是,如果缺少像Nexus Quality Suite或AQTime等分析工具,你将大多处于盲目状态。你的应用程序只是失败了,而内存管理代码则报告没有足够的内存。有可能是你在某个地方做了一些正在破坏你的内存堆的事情。也有可能是你的32位进程为你分配了太多内存。你的计算机可能没有足够的实际或虚拟内存,或者你正在分配一个巨大的数据结构,而你没有意识到它在你的应用程序中不实用。

好的,我会尝试使用 AQTime,谢谢! - Hamish_Fernsby
AQTime的价格相当昂贵,过去试用版对我帮助很大。还有http://www.prodelphi.de/。 - UnDiUdin
1
尝试使用Nexus质量套件,或者免费的想法是使用完整调试模式的FastMM,并使用堆转储来确定哪些内容正在使用您的内存。 - Warren P

2

您是否已安装完整版的FastMem内存管理器?它可以帮助您追踪内存处理中的错误。请检查一下是否有泄漏。

如果没有泄漏,那么您可能面临着相当严重的碎片问题,您需要通过维护对象池而不是不断地分配/释放对象来解决它。


2

要找到OutOfMemory异常的原因,您需要查看所有对象的创建情况,而不仅仅是异常发生的地方。

像EurekaLog这样的第三方工具可以显示应用程序中实例化的所有对象以及未正确处理的对象。您可以尝试使用try finally块和FreeAndNil过程来纠正它们。


最近我添加了很多Create / Free来减少很少使用的表单的内存开销,所以这可能是问题所在。谢谢! - Hamish_Fernsby

2
听起来像是内存泄漏。
我总是添加一个


  {$IFDEF DEBUG}
    ReportMemoryLeaksOnShutdown := DebugHook <> 0;
  {$ENDIF}

为我的调试构建项目源文件。

这能很好地指示我编译程序的质量。


0
你需要按照 "debugging" 部分所述的设置项目选项,以便能够调试您的程序。
一旦你这样做了,请重新构建您的程序,并在下次发生错误时,您将能够逐步检查您的代码。使用Watches(Ctrl+Alt+W),您应该能够看到您分配了多少内存以及它的位置。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接