要GC.Collect与否?

时间:2011-03-09 16:27:36

标签: .net garbage-collection clr

阅读这篇陈旧但经典的文档Writing High-Performance Managed Applications - A Primer,我发现了以下声明

  

GC是自我调整的,可根据应用程序内存要求进行自我调整。在大多数情况下,以编程方式调用GC将阻碍调整。通过调用GC.Collect“帮助”GC很可能无法提高应用程序性能

我正在处理在给定时间点内消耗大量内存的应用程序。当我在使用该内存的代码中完成时,我正在调用GC.Collect。如果我不这样做,我会出现内存异常。这种行为是不一致的,但在30%的时间里,我得到了一个内存不足。添加GC.Collect后,我从来没有得到这个内存不足的异常。即使这个最佳实践文件建议反对,我的行动是否合理?

4 个答案:

答案 0 :(得分:29)

GC中发生的部分原因是内存中的对象是世代,因此早期的收集频率比其他对象更频繁。这有助于通过不尝试一直收集长寿命对象来节省性能。

考虑到这一点,当您自己致电GC.Collect()时,可能会发生两件事。首先,您最终花费更多时间进行收藏。这是因为除了手动GC.Collect()之外,仍会发生正常的背景集合。第二个是你会留在内存更长的,因为你强迫一些东西进入一个不需要去那里的更高阶代。换句话说,自己使用GC.Collect()几乎总是一个坏主意。

在某些情况下,垃圾收集器并不总是表现良好。其中之一是大对象堆。对于大于特定大小的对象(80,000字节,IIRC,但现在可能已经过时),这是一个特殊的生成。这一代几乎从未被收集过,而且几乎从未压缩。这意味着随着时间的推移,你最终可能会在内存中出现许多不会被释放的漏洞。物理内存实际上并未使用,而可用于其他进程,但它仍然消耗您进程中的地址空间,默认情况下,您的限制为2GB。

这是OutOfMemory异常的一个非常常见的来源 - 您实际上并没有使用那么多内存,但是您在大对象堆中的漏洞占用了所有这些地址空间。到目前为止,这种情况最常见的方式是重复附加到大字符串或文档。这可能不是你,因为在这种情况下,对GC.Collect()的任何调用都不会包含LOH,但在你的情况下它似乎有帮助。但是,这是我见过的绝大多数OutOfMemory异常的来源。

垃圾收集器并不总是表现良好的另一个地方是某些事情会导致对象保持root状态。一个例子是事件处理程序可以防止收集对象。解决此问题的方法是确保订阅事件的每个+=操作都有相应的-=操作来取消订阅它。但同样,GC.Collect()不太可能在这里提供帮助 - 该对象仍然存在于某个地方,因此无法收集。

希望这为您提供了一个调查途径,以解决导致需要首先使用GC.Collect()的潜在问题。但是,如果不是,那么拥有一个有效的程序比一个失败的程序更好。在任何地方我都使用GC.Collect(),我会确保代码有充分的文档记录,说明你需要它的原因(没有异常)和完全步骤和数据可靠地重现它因此,未来可能想要删除它的程序员可以确定何时安全。

答案 1 :(得分:7)

大多数人会说让代码正常工作比快速编写代码更重要。因此,当你不致电GC.Collect()时,它无法在30%的时间内工作,那就胜过所有其他问题。

当然,这会导致更深层次的问题:“为什么会出现OOM错误?是否有更深层次的问题需要修复,而不是仅仅调用GC.Collect()

但是你发现的建议谈到了表现。如果它使您的应用程序在30%的时间内失败,您是否关心性能?

答案 2 :(得分:2)

一般来说,GC.Collect不是必需的。如果您的图片存在于非托管内存中,请务必正确使用GC.AddMemoryPressureGC.RemoveMemoryPressure

答案 3 :(得分:0)

从您的描述中听起来似乎Dispose没有处理对象,或者您没有在操作之前设置将被替换为null的成员值。作为后者的一个例子:

  • 获取表格,以网格显示
  • (用户点击刷新)
  • 在刷新数据时禁用表单
  • 查询返回,新数据填充在网格中

你可以在过渡期间清除网格,因为它无论如何都要被替换;如果不这样做,你将暂时在内存中(不必要地)使用这两个表。