如何调试GDI对象泄漏?

5

我不太确定如何进行这项工作。这是一个大型应用程序,我们的大多数表单都存在GDI对象“泄漏”问题。

是否有工具可以帮助解决这个问题?是否有关于如何使用此类工具的教程?

我应该从我们的表单中开始删除代码,直到找出罪犯吗?(代码非常多)。


2
你如何知道你的程序存在GDI对象泄漏? - Jim Mischel
@JimMischel 如果我们打开和关闭一些表单多次(在崩溃前我们最常使用的表单被打开/关闭了大约75次),我们的应用程序将会崩溃。 我忘记确切的错误是什么,但它与Windows中的1,000个句柄限制有关(1,000是一个注册表设置,可以增加到约32,000)。我已经断断续续地看了几个月了(我似乎无法确定我们做错了什么,因此我无法弄清楚如何解决它)。 - XenoPuTtSs
@XenoPuTtSs 听起来你可能需要使用adplus来进行内存转储,并使用winDbg和SOS来查看根本原因。当然,具体的错误信息会非常有帮助。 - Jeremy Thompson
1
所以你不一定是泄漏GDI对象,而是句柄,这些句柄可能是GDI对象、用户控件等。听起来好像你的程序中有东西在关闭窗体时保持对该窗体或者窗体上的某些控件的引用。这将防止窗体(或引用的控件)被正确处理。你是否有一个持久的集合,在某个地方保存了对窗体或控件的引用? - Jim Mischel
4个回答

18

超过GDI对象的10,000个限制是相当罕见的,当您不主动调用Dispose()方法时,垃圾回收器会自动处理它们。更有可能发生的故障模式是超出窗口的对象限制。在Winforms中很容易做到,当您未显式处理已移除的控件时,Controls.Clear()或Controls.Remove()将使您快速达到该限制。垃圾收集器无法清理它们。

您可以从Taskmgr.exe的Processes选项卡中获得良好的诊断。选择查看+选择列并勾选Handles、USER对象和GDI对象。在使用过程中观察进程的这些数字。一个稳步上升的数字表明您将在拒绝为您提供更多资源时让Windows炸毁您的程序。默认配额为每个对象类型为10000。USER对象表示您的Controls.Clear/Remove存在问题,GDI对象表示您正在泄漏System.Drawing对象。Perfmon.exe是一个很好的工具,可查看垃圾回收器是否运行得足够频繁以释放未处理的System.Drawing对象。

普遍认为,在需要时显式调用Dispose()是一种良好的惯例,特别是对于Image和Bitmap对象,它们占用非常少的GC内存但占用大量的非托管内存,当您不处理它们时,很容易让程序累积OOM错误。请注意Properties.Resources中的一些陷阱,每次使用时都会得到一个新对象并且需要进行处理。在绘图代码中始终使用using语句。


使用Controls.Clear()或Controls.Remove()可以快速清除控件,当您没有明确处理已删除的控件时。垃圾回收器无法清理它们。<< 真的吗?为什么没有人指出来,这样gcoll就可以捡起来了,为什么不呢?
- TheBlastOne
3
移除的控件通过将它们作为子窗口附加到名为“停车窗口”的隐藏窗口中而保持活动状态,以便准备重新附加到另一个窗口。这是非常好的一项功能,因为它可以保持本机窗口不变。但如果没有重新附加或者未被处理,它们将永远泄漏。 - Hans Passant
我正在使用taskmgr.exe,并且观察到我的GDI对象不断增加。最终它达到了一个限制(10,000),导致应用程序崩溃(顺便说一下,这个10,000可以通过注册表"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota"进行配置)。“停车窗口”这个术语是否官方?如果不是,这个功能叫什么? - XenoPuTtSs
忘记停车窗口,它不是你问题的原因。注册表修改是无用的,它只是推迟了不可避免的结果。你需要找到漏洞。在关注GDI对象计数器的同时单步绘制代码。建议使用好的分析工具。 - Hans Passant
@HansPassant,你知道有什么好的分析器吗?我提到注册表更改只是因为降低数字帮助我及时重现错误。 - XenoPuTtSs
2
非常好的功能,因为它保持了本地窗口的完整性。我不明白为什么不能保留对稍后重新父级化的控件的引用。停车窗口难道不是一种“有用”的预防机制,恰好在你可以轻松自己保留引用的情况下使用吗?这是否有文档记录? - TheBlastOne

1

1
原来我只是使用了任务管理器并尝试重现问题。我们应用程序的问题(以及GDI对象泄漏)是我们正在使用静态对象并绑定到它。至少在.NET 3.5中存在一个错误,即当窗体关闭时,它将不知道如何正确处理所有内容,并且某些图形对象将保留在内存中。

1
你能提供你说话的来源吗?这个 bug 已经被报告了吗?我们能追踪它吗?谢谢。 - sebagomez
我在MSDN上提交了一个问题,但被告知不会修复。但是在我的另一个问题(http://stackoverflow.com/questions/8343109/possible-bug-with-net-winforms-or-am-i-just-missing-something-gdi-object-leak)中,我发布了能够重现此问题的完整代码。 - XenoPuTtSs

1

您可以使用 Perfmon (Windows->开始->Perfmon) 监视 GDI 泄漏。它具有监视并将数据记录在 CSV 文件中的功能。


GDI对象似乎无法被性能计数器跟踪:https://technet.microsoft.com/zh-cn/library/cc958260.aspx - xverges

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