Best Practice for Forcing Garbage Collection in C#

根据我的经验,似乎大多数人都会告诉你,强制执行垃圾收集是不明智的,但是在某些情况下,如果您正在处理的大型对象并不总是在0代中收集,但是内存是一个问题,那么强制执行收集是否可行?有没有这样做的最佳实践?

160133 次浏览

我已经学会了不要试图智取垃圾回收。也就是说,在处理非托管资源(如文件 I/O 或数据库连接)时,我只是坚持使用 using关键字。

不确定这是否是一个最佳实践,但是当在一个循环中处理大量的图像时(例如,创建和处理大量的图形/图像/位图对象) ,我经常让 GC.Collect 收集。

我记得在某个地方读到过 GC 只在程序(大多数情况下)处于空闲状态时才运行,而不是在密集循环的中间运行,因此这可能看起来像是手动 GC 可以运行的区域。

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

  1. 您的代码中是否存在声明的项的范围大于所需范围的情况
  2. 是内存使用真的太高了
  3. 比较使用 GC.Collect ()之前和之后的性能,看看它是否真的有帮助。

最佳实践是不强制执行垃圾回收。

根据 MSDN:

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

但是,如果可以可靠地测试代码以确认调用 Collect ()不会产生负面影响,那么可以继续..。

只要确保当你不再需要它们的时候,它们已经被清理干净了。如果您有自定义对象,请考虑使用“ using 语句”和 IDisposable 接口。

这个链接有一些关于释放内存/垃圾收集等的实用建议:

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

大对象是在 LOH (大对象堆)上分配的,而不是在第0代上。如果你是说他们没有被垃圾收集到第0代,你是对的。我相信它们只有在完整的 GC 循环(第0、1和2代)发生时才会被收集。

也就是说,我相信在另一方面,当处理大型对象和内存压力上升时,GC 将更积极地调整和收集内存。

It is hard to say whether to collect or not and in which circumstances. I used to do GC.Collect() after disposing of dialog windows/forms with numerous controls etc. (because by the time the form and its controls end up in gen 2 due to creating many instances of business objects/loading much data - no large objects obviously), but actually didn't notice any positive or negative effects in the long term by doing so.

假设您的程序没有内存泄漏,对象堆积,不能在第0代进行 GC-ed,因为: 1)它们被引用了很长时间,因此进入了 Gen1和 Gen2; 2) They are large objects (>80K) so get into LOH (Large Object Heap). And LOH doesn't do compacting as in Gen0, Gen1 & Gen2.

Check the performance counter of ".NET Memory" can you can see that the 1) problem is really not a problem. Generally, every 10 Gen0 GC will trigger 1 Gen1 GC, and every 10 Gen1 GC will trigger 1 Gen2 GC. Theoretically, GC1 & GC2 can never be GC-ed if there is no pressure on GC0 (if the program memory usage is really wired). It never happens to me.

对于问题2) ,您可以检查“ .NET Memory”性能计数器来验证 LOH 是否正在膨胀。如果这确实是您的问题,也许您可以创建一个大型对象池,就像本博客建议的 http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx那样。

One more thing, triggering GC Collect explicitly may NOT improve your program's performance. It is quite possible to make it worse.

那个。NET GC 的设计和调整是适应性的,这意味着它可以根据程序内存使用的“习惯”调整 GC0/1/2阈值。因此,经过一段时间的运行,它将适应您的程序。一旦显式调用 GC.Collection,阈值将被重置!还有。NET 必须花时间再次适应你的程序的“习惯”。

我的建议永远是信任。NET GC.如果出现任何内存问题,请检查“ .NET Memory”性能计数器并诊断我自己的代码。

从这个角度来看——当垃圾桶只剩10% 的时候扔掉厨房垃圾是更有效率呢,还是先把它装满再扔出去更有效率呢?

不让垃圾桶填满,你就是在浪费时间往返于外面的垃圾桶。这类似于 GC 线程运行时发生的情况——所有托管线程在运行时都被挂起。如果我没有弄错的话,GC 线程可以在多个 AppDomain 之间共享,因此垃圾收集会影响所有 AppDomain。

当然,你可能会遇到这样的情况: 你不会在短时间内往垃圾桶里添加任何东西——比如说,如果你要去度假的话。那么,在出去之前把垃圾扔掉是个好主意。

这可能是一个强制 GC 可以帮助-如果您的程序空闲,使用的内存不是垃圾收集,因为没有分配。

但是,如果可以可靠地测试代码以确认调用 Collect ()不会产生负面影响,那么可以继续..。

恕我直言,这类似于说 “如果你能证明你的程序将来不会有任何错误,那么请继续...”

严肃地说,强制使用 GC 对于调试/测试目的是有用的。如果您觉得需要在任何其他时间执行此操作,那么要么是您错了,要么是您的程序编译错了。不管怎样,解决方法不是强迫 GC..。

我认为 Rico Mariani给出的示例很好: 如果应用程序的状态发生重大变化,那么触发 GC 可能是合适的。例如,在文档编辑器中,当文档关闭时可以触发 GC。

在编程中,很少有一般的指导方针是绝对的。有一半的时间,当有人说“你做错了”,他们只是在滔滔不绝地讲一些教条。在 C 语言中,它过去害怕程序自修改或线程之类的东西,在 GC 语言中,它强迫 GC 或者阻止 GC 运行。

正如大多数指导方针和良好的经验法则(以及良好的设计实践)一样,在很少的情况下,绕过既定的规范工作是有意义的。你必须非常确定你了解这个案子,你的案子真的需要废除常规做法,你了解你可能造成的风险和副作用。但确实有这种情况。

编程问题多种多样,需要灵活的方法。我见过这样的情况: 在垃圾收集语言中阻塞 GC 是有意义的,而在某些地方触发 GC 是有意义的,而不是等待它自然发生。95% 的情况下,这两种情况都是没有正确解决问题的标志。但是20个案例中有1个案例可以证明这一点。

最佳实践是在大多数情况下不强制执行垃圾回收。 (我使用过的每个强制进行垃圾收集的系统都有下划线问题,如果解决了这些问题,就不需要强制进行垃圾收集,从而大大提高了系统的速度。)

you比垃圾收集器更了解内存使用情况时,就会出现 几个案子。在多用户应用程序中,或者在一次响应多个请求的服务中,这种情况不太可能发生。

然而,在某些 批处理批处理类型处理中,你比 GC 知道的更多。例如,考虑一个应用程序。

  • 在命令行上给出一个文件名列表
  • 处理单个文件,然后将结果写出到结果文件。
  • 在处理文件时,创建大量相互关联的对象,这些对象在文件处理完成之前无法收集(例如,解析树)
  • 不会在它处理的文件之间保持很多状态

在仔细测试之后,可以证明在处理每个文件之后应该强制执行完整的垃圾回收。

Another cases is a service that wakes up every few minutes to process some items, and does not keep any state while it’s asleep. Then forcing a full collection just before going to sleep be worthwhile.

唯一一次我会考虑强迫 收藏就是当我知道很多的时候 最近被创造出来的物体 目前只有很少的物体 引用。

我宁愿有一个垃圾收集 API,当我可以提示它这种类型的事情,而不必强制 GC 我自己。

另见“ Rico Mariani 的演出花絮

不知道这是不是最好的做法。

建议: 不确定时不要实施这个或任何东西。当事实已知时重新评估,然后在性能测试之前/之后执行以验证。

我最近遇到的一个需要手动调用 GC.Collect()的情况是,在处理大型 C + + 对象时,这些对象被包装在微小的托管 C + + 对象中,而这些对象又是从 C # 访问的。

垃圾收集器从未被调用,因为所使用的托管内存量可以忽略不计,但非托管内存的使用量非常大。在对象上手动调用 Dispose()将需要我自己跟踪对象不再需要的时间,而调用 GC.Collect()将清除不再被引用的任何对象... ..。

我想补充一点: 调用 GC.Collect ()(+ WaitForPendingFinalizer ())是故事的一部分。 正如其他人所正确提到的,GC.Collect ()是非确定性集合,由 GC 本身(CLR)自行决定。 即使添加对 WaitForPendingFinalizer 的调用,它也可能不是确定性的。 从这个 msdn 链接获取代码,并使用对象循环迭代作为1或2运行代码。您将找到非确定性的含义(在对象的析构函数中设置一个断点)。 准确地说,当仅有1个(或2个)延迟对象时,等待. . ()不会调用析构函数

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

下面是一个有趣的例子:

注意 : 如果您已经在 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 = "";
}
}

我建议,首先分析一下输出可能是什么,然后运行,然后阅读下面的理由:

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

我不建议人工垃圾收集。我向您保证,您没有正确处理大型对象。使用 USING 语句。无论何时实例化一个对象,一定要在使用完之后处理掉它。此示例代码创建与 USING 语句的连接。然后它实例化一个运输标签对象,使用它,并正确地处理它。

         Using con As SqlConnection = New SqlConnection(DB_CONNECTION_STRING)
con.Open()


Using command As SqlCommand = New SqlCommand(sqlStr, con)
Using reader As SqlDataReader = command.ExecuteReader()


While reader.Read()
code_here()
End While
End Using
End Using
End Using
Dim f1 As frmShippingLabel
f1 = New frmShippingLabel
f1.PrintLabel()
f1.Dispose()

在一些场景中,当强制执行垃圾收集时,对系统的影响肯定非常小,甚至没有负面影响,例如,在日期滚动/预定的系统不在使用的时间。

除了这种情况之外,您还需要在实现强制收集之前和之后测试代码的性能,以确保它实际上是有益的。