9

我有一个似乎泄漏内存的 WindowsForms 应用程序,因此我使用 Redgate 的 ANTS Memory Profiler 来查看我怀疑的对象,发现它们仅由已在Finalizer Queue上的对象持有。太好了,究竟什么是终结器队列?你能指出我最好的定义吗?你能分享一些轶事建议吗?

此外,Finalizer Queue 上的所有根 GC 对象都是名为“caller”的System.Windows.Forms.Control+ThreadMethodEntry对象的实例。我看到它涉及多线程UI交互,但除此之外我知道的不多。原谅我明显的懒惰和承认的无知,但这些资源都埋在供应商的组件中。我正在与供应商讨论这些问题,但我需要一些指导来加快对话速度。你能指出我对 ThreadMethodEntry 最有用的定义吗?有什么轶事建议吗?

另外,我什至应该关注终结器队列中的这些对象吗?

更新:这篇红门文章很有帮助。

4

3 回答 3

20

终结器队列包含所有定义了终结器方法的对象。回想一下,终结器是一种收集非托管资源(如句柄)的方法。当垃圾收集器收集垃圾时,它会将任何带有终结器的对象移动到终结器队列中。在稍后的某个时刻——取决于内存压力、GC 启发式和月相——当垃圾收集器决定收集这些对象时,它会沿着队列向下走并运行终结器。

过去处理过内存泄漏,在终结器队列中看到一堆供应商的对象可能是草率的代码,但这并不表示内存泄漏。通常,好的代码会公开一个 Dispose 方法,该方法将收集托管和非托管资源,并在这样做时通过GC.SuppressFinalize(). 因此,如果供应商的对象确实实现了 Dispose 方法,而您的代码没有调用它,则可能会导致终结器队列中出现一堆对象。

您是否尝试过在 ANTS 中创建两个时间点之间的快照并比较它们之间创建的对象?这可以帮助您识别任何被泄露的托管对象。

此外,如果您想查看运行终结器时内存是否消失,请尝试以下方法进行测试:

System.GC.Collect();
System.GC.WaitForPendingFinalizers(); // 此方法在运行终结器时可能会阻塞
System.GC.Collect();

我不建议正常运行此代码。如果您刚刚完成了大量工作并产生了大量垃圾,您可能想要运行它。例如,在我们的应用程序中,我们的一个函数可以创建大约 350 MB 的垃圾,这些垃圾在关闭 MDI 窗口后会被浪费掉。由于已知这会留下大量垃圾,因此我们手动强制进行垃圾收集。

另请注意,基本 Windows.Forms 代码中有一个低级属性缓存,它将保留最后打开的模式对话框。这可能是内存泄漏的来源。摆脱这个引用的一个可靠方法是强制出现另一个简单的对话框,然后运行上面的 GC 代码。

于 2009-08-12T20:47:30.487 回答
1

终结器队列是一个队列,其中不再使用的对象实例正在等待 GC 终结。此队列中的所有对象都将被最终确定,并且您的内存泄漏可能不会直接来自这些对象之一。但是,这些对象之一可能不会释放其所有非托管资源。

ThreadMethodEntry 类是 IAsyncResult 的实现,此类的实例通常在调用异步操作时创建,例如使用 Invoke 更新 UI 或使用 Begin*/End* 方法。

于 2009-08-12T20:30:51.847 回答
0

这是一篇很好的博客文章,描述了类似的问题。在更技术层面上,您可以考虑使用 SOS.dll(博客文章中描述的)和Sosex.dll来帮助您找出为什么这些 ThreadMethodEntry 对象在内存中徘徊。这些 WinDbg 扩展中有一些命令可以跟踪哪些其他对象正在引用内存中的特定对象。

于 2009-08-12T20:52:00.820 回答