C#中强制垃圾收集的最佳实践

根据我的经验,似乎大多数人会告诉你,强制垃圾收集是不明智的,但是在某些情况下,如果你使用的是并不总是在第0代中收集的大对象,但是内存是一个问题,它可以强制收集? 有没有最好的做法呢?

最好的做法是不强制垃圾收集。

根据MSDN:

“通过调用Collect来强制垃圾收集是可能的,但大多数情况下,这应该避免,因为它可能会造成性能问题。”

但是,如果您可以可靠地testing您的代码以确认调用Collect()不会产生负面影响,那么请继续…

只要尝试确保在不再需要物体时清理物体。 如果您有自定义对象,请使用“使用语句”和IDisposable接口。

关于释放内存/垃圾收集等方面,这个链接有一些很好的实用build议:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

以这种方式来看 – 垃圾桶在10%时扔掉厨房垃圾,或者在拿出垃圾桶之前填满厨房垃圾更有效率吗?

通过不让它填满,你正在浪费你的时间走出垃圾箱外面。 这类似于GC线程运行时发生的情况 – 所有托pipe线程在运行时都处于暂停状态。 如果我没有弄错,GC线程可以在多个AppDomain之间共享,所以垃圾回收会影响所有的AppDomain。

当然,你可能会遇到一个情况,你不会在垃圾箱里随时添加任何东西 – 比如说,如果你打算去度假的话。 那么,出门前把垃圾扔掉是个好主意。

这可能是一次强迫一个GC可以帮助 – 如果你的程序闲置,正在使用的内存不被垃圾收集,因为没有分配。

最佳实践是在大多数情况下不强制垃圾收集。 (我所从事的每一个系统都强制垃圾收集,强调的问题是,如果解决了这个问题,就不需要强制垃圾收集,并且使系统大大加速。)

几种情况,知道更多的内存使用情况,然后垃圾收集器。 在多用户应用程序或者一次响应多于一个请求的服务中,这不太可能是真实的。

但是在某些批处理types的处理中,您确实知道更多的GC。 例如考虑一个应用程序。

  • 在命令行中给出了一个文件名列表
  • 处理单个文件,然后将结果写入结果文件。
  • 在处理文件时,创build了很多相互关联的对象,直到文件处理完成(如分析树)才能被收集,
  • 在处理的文件之间不保持匹配状态

可能会(经过仔细的)testing,你应该强制一个完整的垃圾回收后处理每个文件。

另一种情况是每隔几分钟醒来处理一些物品的服务,在睡眠时不保持任何状态 。 然后在睡觉之前强制收集完整的东西可能是值得的。

我只考虑强制一个集合的时候,是因为我知道最近有很多对象被创build,并且很less有对象被引用。

我宁愿有一个垃圾收集API,当我可以给它暗示这种types的事情,而不必强迫我自己的GC。

另见“ Rico Mariani的表演花絮 ”

我认为Rico Mariani给出的例子很好:如果应用程序的状态有重大变化,那么触发一个GC可能是合适的。 例如,在文档编辑器中,closures文档时触发GC可能没有问题。

在编程中几乎没有一个通用的准则是绝对的。 有一半时间,当有人说'你做错了'时,他们只是吐出一定的教条。 在C语言中,它曾经担心类似于自修改代码或线程的东西,在GC语言中它迫使GC或者阻止GC运行。

与大多数指导方针和良好的经验法则(以及良好的devise实践)一样,在极less数情况下,为了解决既定的规范是有意义的。 你必须非常确定你理解这个案子,你的案件确实需要废除惯例,并且你明白你可能造成的风险和副作用。 但是也有这种情况。

编程问题差异很大,需要灵活的方法。 我已经看到了在垃圾回收的语言中阻塞GC的情况,以及触发它的有意义的地方,而不是等待它自然发生。 95%的时间,这些都是没有接近问题的路标。 但20年一次,可能有一个有效的例子。

我学会了不要试图超越垃圾收集。 就这样说,我只是坚持using关键字处理非托pipe资源,如文件I / O或数据库连接。

不知道这是否是最佳做法,但是当在循环中处理大量图像(即创build和处理大量graphics/图像/位图对象)时,我经常让GC.Collect。

我认为我在某个地方看到GC只在程序(大部分)闲置时才运行,而不是在密集循环的中间,所以可能看起来像手动GC有意义的区域。

最近遇到的一种情况是,需要对GC.Collect()进行手动调用时,使用的是包含在小型托pipeC ++对象中的大型C ++对象,而C ++对象又是从C#访问的。

垃圾收集器从来没有被调用过,因为所使用的pipe理内存的数量可以忽略不计,但是非托pipe内存的使用量是巨大的。 在对象上手动调用Dispose()将要求我跟踪不再需要对象的时间,而调用GC.Collect()将清除不再涉及的任何对象…..

我想你已经列出了最好的做法,除非真的有必要,否则不要使用它。 我强烈build议您更详细地查看您的代码,如果需要,可能会首先使用分析工具来回答这些问题。

  1. 你的代码中有什么东西是在比需要更大的范围声明项目
  2. 内存使用真的太高了
  3. 比较使用GC.Collect()之前和之后的性能是否真的有帮助。

大对象分配在LOH(大对象堆)上,而不是在0上。如果您说他们没有使用gen 0进行垃圾回收,那么您是对的。 我相信只有在完整的GC循环(0代,1代和2代)发生时才收集它们。

也就是说,我相信另一方面,当你使用大型物体时,GC会更积极地调整和收集内存,并且内存压力正在上升。

很难说是否收集和在哪些情况下。 我曾经做过GC.Collect()处理对话窗口/表单与众多的控件等(因为当forms和它的控制结束在第2代,由于创build业务对象/加载大量数据的许多实例 – 没有大的物体),但从长远来看实际上并没有注意到任何积极或消极的影响。

假设你的程序没有内存泄漏,对象积累并且不能在Gen 0中进行GC-ed,因为:1)它们被长时间引用,因此进入Gen1&Gen2; 2)它们是大的物体(> 80K),因此进入LOH(大物体堆)。 而LOH并不像Gen0,Gen1和Gen2那样进行压缩。

检查“.NET内存”的性能计数器,你可以看到1)问题真的不是问题。 通常,每10个Gen0 GC将触发1个Gen1 GC,每10个Gen1 GC触发1个Gen2 GC。 从理论上讲,如果GC0上没有压力(如果程序存储器的使用真的是有线的),则GC1和GC2将不能被GC编辑。 它从来没有发生在我身上。

对于问题2),您可以检查“.NET Memory”性能计数器,以validationLOH是否变得臃肿。 如果问题真的存在,那么也许你可以创build一个大对象池,就像这个博客所build议的那样http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx

我想补充一点:调用GC.Collect()(+ WaitForPendingFinalizers())是故事的一部分。 正如其他人正确地提到的那样,GC.COllect()是非确定性的集合,由GC自己决定(CLR)。 即使您添加对WaitForPendingFinalizers的调用,也可能不确定。 从这个msdn 链接获取代码,并将对象循环迭代的代码运行为1或2.您将find非确定性方法(在对象的析构函数中设置断点)。 正确地说,析构函数在Wait()中只有1个(或2个)滞留对象时不会被调用。()。[Citation reqd。]

如果您的代码正在处理非托pipe资源(例如:外部文件句柄),则必须实施析构函数(或终结器)。

这是一个有趣的例子:

注意 :如果您已经从MSDN尝试了上述示例,则下面的代码将清除空白。

 class Program { static void Main(string[] args) { SomePublisher publisher = new SomePublisher(); for (int i = 0; i < 10; i++) { SomeSubscriber subscriber = new SomeSubscriber(publisher); subscriber = null; } GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine(SomeSubscriber.Count.ToString()); Console.ReadLine(); } } public class SomePublisher { public event EventHandler SomeEvent; } public class SomeSubscriber { public static int Count; public SomeSubscriber(SomePublisher publisher) { publisher.SomeEvent += new EventHandler(publisher_SomeEvent); } ~SomeSubscriber() { SomeSubscriber.Count++; } private void publisher_SomeEvent(object sender, EventArgs e) { // TODO: something string stub = ""; } } 

我build议,首先分析输出结果,然后运行,然后阅读下面的原因:

{析构函数只有在程序结束时才被隐式调用。 }为了确定性地清理对象,必须实现IDisposable并对Dispose()进行显式调用。 这是本质! 🙂

还有一件事情,明确触发GC Collect可能不会提高你的程序的性能。 这很可能使情况变得更糟。

.NET GCdevise良好,并且被调整为自适应,这意味着它可以根据程序内存使用的“习惯”来调整GC0 / 1/2阈值。 所以,它会在一段时间后适应你的程序。 一旦明确调用GC.Collect,阈值将被重置! .NET必须花时间去适应程序的“习惯”。

我的build议总是相信.NET GC。 任何内存问题表面,检查“.NET内存”性能计数器和诊断我自己的代码。

不知道这是否是最佳做法…

build议:不确定时不要执行此操作或任何操作。 当事实已知时重新评估,然后在性能testing之前/之后执行validation。

但是,如果您可以可靠地testing您的代码以确认调用Collect()不会产生负面影响,那么请继续…

恕我直言,这就类似于说: “如果你能certificate你的程序将来永远不会有任何错误,那就继续吧…”

严肃地说,强制GC对于debugging/testing目的是有用的。 如果你觉得在其他任何时候都需要这样做,那么要么你错了,要么就是你的程序被搞错了。 无论哪种方式,解决scheme是不强迫GC …