追踪EOutOfResources错误

12

问题:

有没有一种简单的方法来获取运行应用程序中泄漏资源的类型列表?也就是说,通过连接到应用程序?

我知道Memproof可以做到这一点,但它会使应用程序变得如此缓慢,以至于应用程序甚至连续运行一分钟都难以实现。大多数任务管理器类似的工具可以显示数量,但不显示类型。

检查本身造成的影响很严重(停止应用程序进程)并不是问题,因为如果我接近极限,我可以使用任务管理器检查它(或者至少我希望如此)

欢迎提供有关资源泄漏狩猎(而不是内存)的任何其他见解。

背景:

我有一个Delphi 7/2006/2009应用程序(可编译所有三个版本),大约几周后,它开始表现出古怪的行为。但是,在它运行的其他几个系统中,它可以一直运行直到电源关闭。

我尝试加入一些调试代码来缩小问题范围。并发现异常是在文件保存时引发的EOutofResources(文件保存可能会发生数千次/天)。

我已经尝试推断出内存泄漏(使用FastMM),但由于数据流非常高(来自千兆位工业相机的60MB / s),我只能排除“缓慢”内存泄漏,而不能快速闪烁的内存泄漏在发生时耗尽内存。如果出现问题,应用程序将在不到半分钟的时间内填满内存。

主要嫌疑人是一些出错时留下的文件句柄和TMetafiles(它们被流式传输到这些文件中)。次要嫌疑人是VST、弹出菜单和Tframes。

更新:

另一个可能的提示:它在D7上运行了两年,并且现在问题出现在Turbo Explorer上(我用它来处理未转换为D2009的稳定项目)。

Paul-Jan:由于信息获取速度慢,每周仅发生一次(可能会在晚上发生),因此我需要结合一些内容来为星期四的到来做准备。简而言之,我不确定100%。我打算带上整个Systemtools集合,看看是否能找到一些东西(因为这样它就可以运行数天)。还有一种可能是我会看到一些打开的文件。(也许应该尝试找一些mingw lsof并调度它)

但是这个应用程序很少有GUI操作(它是一个机器视觉检查应用程序),除了屏幕刷新+/- 15/s,其中包括tbitmap stretchdraw + tmetafile,但是当我保存到磁盘时(TFileStream),我会遇到这个错误,句柄可能真的耗尽了。然而,在同一个流中,TMetafile也被保存到流中,后来的应用程序都没有了,它们可以运行数月。

------------------- 更新

我已经搜索了很多次,并设法在体外重现了两三次问题。当memusage约为256MB(系统有2GB),用户对象为200,gdi对象为500时,就会出现问题,没有比预期更多的文件打开)。这不是非常特殊的情况。我注意到我会泄漏一些句柄,可能是由于重新分配框架(VCL中的某些功能似乎会泄漏HPalette's)造成的,但我认为根本原因是不同的问题。我重复使用TMetafile,并在其中清除。我认为清除元文件并没有真正(总是?)调整资源大小,最终池中的每个元文件都达到了最大大小,并且具有20-40个以上的TMetafile(每个可以是几百KB),这将超过桌面堆限制。

这是一个理论,但我会尝试通过将客户的桌面限制设置为10MB来验证这一点,但在确认是否有任何变化之前需要数周时间。这个理论也证实了为什么这台机器很特别(可能这台机器自然地拥有稍大的平均元文件)。偶尔释放并重新创建池中的TMetafile也可能有所帮助。

幸运的是,这些问题(包括tmetafile和reparenting)在新一代应用程序中已经被设计出来了。
由于特殊情况(以及我非常有限的测试窗口),这可能需要一段时间,但我决定暂时将桌面堆作为示例(尽管GDILeaks也有一定用处)。
审计还发现了线程中使用GDI类型(虽然只保存未被使用或连接的tmetafile到流中)。
------------- 更新2。
增加桌面限制似乎只稍微增加了发生问题的时间。
不幸的是,我无法进一步跟进此问题,因为机器已更新到不再有此问题的新版本框架。
总之,从旧框架到新框架,三个核心修改如下:
- 我不再通过重新定位框架来更改屏幕。我现在使用隐藏和显示表单。我改变了这个,因为我也偶尔会由于这个原因遇到非常罕见的崩溃或异常(可以通过点击解决)。但所有的崩溃都是在操作GUI时发生的,而不是像主要问题那样自发发生。 - 发生崩溃的程序例程涉及TMetafile。TMetafile已被设计出,并被一个更简单的自制格式所取代。(基本上是带有Opengl顶点的数组) - 绘图不再使用带有tmetafile叠加的tbitmap进行拉伸绘制,而是使用OpenGL。
当然,也可能是其他东西,在重写上述部分时发生了变化,修复了一些非常严重的详细错误。它必须是一个极其糟糕的错误,因为我尽可能地分析了上述系统。 更新于2012年11月 在私人邮件讨论后:回顾一下,下一步将是向metafiles对象添加计数器,并在每次x * 1000使用时重新实例化它们,看看是否会改变任何内容。如果您遇到类似的问题,请尝试查看是否可以定期销毁和重新初始化动态分配的长期资源。

虽然任务管理器不能显示哪些句柄正在泄漏,但它确实可以帮助确认是否有句柄正在泄漏。我不确定这是否是情况,也许你可以更新你的答案并提供这个信息? - Paul-Jan
我现在几乎恰好遇到了同样的问题,但没有涉及元文件。 - Warren P
你如何更改桌面限制? - Warren P
同样的错误困扰我已经很长时间了。 - Gabriel
回头看,我怀疑问题出在TMetafile或TBitmap上。它们被绘制在TPanels上,然后可选择性地保存它们。我怀疑那里出了问题。我转向OpenGL,唯一的问题是字体支持痛苦。 - Marco van de Voort
我刚刚加了一个答案,描述了我如何解决这个问题——不直接在主窗体画布上绘制(该画布还包含其他控件)。自从我将所有绘图都放在自己的画布上而不是主窗体的画布上之后,问题就没有再出现过。 - Jerry Dodge
8个回答

13

错误有一丝微小的可能会误导。如果VCL无法为窗口获取DC(详见Controls.pas中的TWinControl.GetDeviceContext),它会天真地报告EOutOfResources

我说"天真"是因为GetDC()可能返回空句柄的其他原因,而VCL应该报告操作系统错误,而不是假设出现了资源不足的情况(这需要进行Windows版本检查才能可靠地实现,但VCL也可以应该负责)。

我曾经遇到过一个情况,结果是窗口句柄无效导致了EOutOfResources错误。一旦我发现了真正的问题,找到原因并修复它就很简单了,但是我浪费了许多时间,试图找到一个不存在的资源泄漏。

如果可能的话,我会检查导致此异常的堆栈跟踪——如果它来自TWinControl.GetDeviceContext,那么问题可能并不是你所认为的(当然,无法确定可能是什么,但消除不可能总是发现解决方案的第一步,无论多么不太可能)。


很好的提示。GetDC返回NULL的原因可能是什么? - Gabriel
@Altar - 最简单的问题,也是在我的情况下困扰我的问题,就是当传递给GetDC()的HWND无效时。在我的情况下,它是一个已经被销毁的窗口。一旦我发现了这个问题[通过自己调用GetLastError()],就很容易看到HWND是如何在我获得DC之前[意外地]被销毁的,并解决了这个问题。 - Deltics

6
如果出现 GDI 句柄泄漏,您可以查看 MSDN Magazine January 2003,该杂志使用了工具 GDILeaks。其他工具包括 GDIObjGDIView。此外,请参见 这里
另一个 EOutOfResources 的来源可能是 桌面堆栈 已满。我在繁忙的终端服务器上使用大屏幕时遇到过这个问题。
如果您正在泄漏大量文件句柄,则可以使用 Process Explorer 并查看进程的打开文件句柄,看是否存在异常情况。或者使用 WinDbg 和 !htrace 命令。

到目前为止最佳答案。按类型显示GDI资源。在接受之前先看看客户怎么说。 - Marco van de Voort
你能指定“这些免费工具”的名称吗?因为现在这个链接无效。 - Zam

3

我以前遇到过这个问题。根据我的了解,Delphi可能随时抛出EOutOfResources错误,每当Windows API返回ERROR_NOT_ENOUGH_MEMORY时(正如其他答案所讨论的那样),Windows可能会因为各种原因返回ERROR_NOT_ENOUGH_MEMORY。

在我的情况下,EOutOfResources是由TBitmap引起的 - 特别是TBitmap对CreateCompatibleBitmap的调用,它使用其默认的pfDevice PixelFormat。显然,即使您的系统有足够的内存和足够的GDI资源,Windows也可能对面向设备的位图可用内存施加相当严格的系统范围限制(请参见,例如,此讨论)。 (这些系统范围的限制显然是因为Windows可能会在视频卡的内存中分配面向设备的位图。)

解决方案很简单,只需使用设备无关位图(DIBs)即可(尽管这可能不会提供很好的性能)。在Delphi中,将TBitmap.PixelFormat设置为除pfDevice之外的任何值即可。此KB文章描述了如何为设备选择最佳DIB格式,尽管我通常只使用pf32Bit而不是尝试确定应用程序显示在每个监视器上的最佳格式。

我检查了一下,源代码在创建后将所有图像设置为pf8bit。重写中使用了自己的图像类型,取代了tbitmap。(基于字节数组)。所以我的猜测是类似的,但是针对tmetafile。 - Marco van de Voort
你还记得“这个邮件列表讨论”的名字吗?链接现在已经失效了。 - Wolf
@Wolf - 我不记得了,但我找到了另一个讨论(或同一讨论的另一个副本)并更新了链接。谢谢。 - Josh Kelley

2

大多数时候我看到的EOutOfResources错误都是一些句柄泄漏引起的。

你尝试过类似MadExcept这样的工具吗?

--jeroen


我知道,这就是为什么我正在寻找一种查找资源泄漏类型摘要的方法。我曾经使用memproof来做到这一点(据我所知,它钩取了很多win32调用来实现这一点),但那会导致速度变慢得太多。 - Marco van de Voort
我建议你专注于堆栈回溯转储并从上下文中找出问题,而不是资源摘要。然而,我相信 AQTime 可能能够将单个资源计数与创建它们的代码行链接起来,这可能对你有用。 - Warren P
2
MadExcept 4 具备捕获资源泄漏的能力(仅适用于32位应用程序)。 - Nicholas Ring

2
我尝试添加了一些调试代码以缩小问题范围。我发现异常是在保存文件时出现的EOutofResources。(文件保存可能每天发生数千次)。
我猜测你是否在使用Windows API (GetTempFileName) 创建临时文件,并且可能会破坏一些文件系统索引或忘记关闭文件句柄?
无论如何,我同意你的假设,认为这可能是一个文件句柄问题。鉴于你的症状和诊断结果,这似乎是最有可能的情况。

没有gettempfilename。我立刻想到了文件句柄,并进行了检查。然而,所有主要的保存都在非常简单和易于概述的线程.execute方法中的try finally块中(所有常见的保存都集中在一个存储线程中,因为它们会阻塞太久)。没有一个线程似乎停止了(未处理的异常会这样做),但是在其中添加日志后开始返回这个(已处理的)EOutofResources。 - Marco van de Voort

0
我今天花了整整一天的时间来解决这个问题。我找到了很多有用的资源,指引我朝着GDI的方向去寻找答案,因为我正在使用GDI+通过定时器/无效/绘制(在单独的线程中执行动画)直接将高速动画呈现在主窗体上。此外,我在这个窗体中还有一个面板,其中包含一些动态创建的控件,供用户对动画进行更改。
这个问题非常随机和突然。它不会在我的代码的任何地方出错,当错误对话框出现时,主窗体上的动画仍然可以正常工作。有一次,两个这样的错误同时弹出(而不是顺序)。
我仔细观察了我的代码,并确保我没有泄漏与GDI相关的句柄。实际上,根据任务管理器的显示,我的整个应用程序通常只保留不到300个句柄。但是,这个错误会随机地弹出。并且它总是与最简单的UI相关操作相对应,例如只是将鼠标移动到标准VCL控件上。 解决方案

我相信我已经通过将绘图逻辑更改为在自定义控件内执行而不是直接在主窗体上执行来解决了这个问题。我认为我之前在同一表单画布上快速绘制,与其他控件共享,某种程度上干扰了它们。现在它有自己专用的画布进行绘制,似乎完美地解决了问题。

至少经过1小时的激烈测试,情况是如此。

Animation Working Without Errors

[祈求好运]


我在面板上绘画,并使用stretchdraw进行绘制。 - Marco van de Voort

0

同时,尝试使用SysInternals的Process Explorer检查应用程序的句柄计数。句柄泄漏可能非常危险,并且会随着时间的推移而逐渐增加。


0

我目前遇到了这个问题,在软件中显然没有泄漏我的任何代码,所以如果有泄漏,它们可能会在组件的源代码或VCL源代码本身中发生。

句柄计数和GDI和用户对象计数不增加,也没有任何东西被创建。Deltic的答案显示出了一些角落情况,其中消息有点误导,而Allen则建议即使是文件写入也可能会导致此错误。

到目前为止,我发现寻找它们的最佳策略是使用JCL JCLDEBUG堆栈跟踪或MadExcept中的异常报告保存功能来生成上下文信息以查明实际失败的原因。

其次,AQTime包含许多工具可帮助您,包括资源分析器,可以保持创建资源的代码和调用方式之间的链接,以及总句柄数量的计数。它可以在运行时抓取结果,因此不仅限于在退出后检测未释放的资源。因此,运行AQTime,在中间运行中进行结果捕获,等待几个小时,然后再进行捕获,您应该有两个时间点来比较句柄计数。以防万一是显而易见的事情。但正如Deltics明智地指出的那样,在可能不应该发生的情况下提出了这个异常类。


回顾过去,我怀疑这与TMetafile有关。 - Marco van de Voort

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