Answers:
最佳做法是不要强制垃圾回收。
根据MSDN:
“可以通过调用Collect来强制进行垃圾收集,但是在大多数情况下,应避免这种情况,因为这可能会导致性能问题。”
但是,如果可以可靠地测试代码以确认调用Collect()不会产生负面影响,请继续...
只需尝试确保在不再需要对象时清理它们。如果您有自定义对象,请考虑使用“ using语句”和IDisposable接口。
该链接在释放内存/垃圾回收等方面有一些很好的实用建议:
TargetOfInvocationNullException
。
这样看-在垃圾桶为10%时将厨房垃圾扔掉或在取出之前让其填满是否更有效率?
通过不让它填满,您在浪费时间往返于外面的垃圾箱。这类似于GC线程运行时发生的事情-所有托管线程在运行时都将挂起。而且,如果我没记错的话,GC线程可以在多个AppDomain之间共享,因此垃圾回收会影响所有这些线程。
当然,您可能会遇到这样的情况,即您很快就不会在垃圾桶中添加任何东西-例如,如果您打算休假。然后,最好在出门前先扔掉垃圾。
这可能是强制GC可以提供帮助的一倍-如果程序空闲,则由于没有分配,因此不会对正在使用的内存进行垃圾回收。
最佳做法是在大多数情况下不要强制垃圾回收。 (我研究过的每个系统都强制执行垃圾收集,并强调了以下问题:如果解决了这些问题,将消除了强制执行垃圾收集的需求,并大大加快了系统运行速度。)
在某些情况下,您比垃圾收集器了解更多有关内存使用的信息。在多用户应用程序或一次响应一个以上请求的服务中,这不太可能是正确的。
但是,在某些批处理中,您对GC的了解更多。例如考虑一个应用程序。
您可能可以进行案例分析(经过仔细的测试),在处理完每个文件后应强制执行完整的垃圾回收。
另一种情况是,该服务每隔几分钟醒来以处理一些项目,并且在睡眠时不保持任何状态。然后在临睡前强制进行一次完整的收集可能是值得的。
我唯一考虑强制收集的时间是当我知道最近创建了很多对象并且当前仅引用了很少的对象时。
当我可以向我提供有关此类事情的提示而不必强迫自己使用GC时,我宁愿拥有一个垃圾收集API。
另请参阅“ Rico Mariani的演奏花絮 ”
我认为Rico Mariani给出的示例很好:如果应用程序状态发生重大变化,则可能触发GC。例如,在文档编辑器中,关闭文档时可以触发GC。
几乎没有绝对的通用编程准则。一半时间,当有人说“你做错了”时,他们只是在吐出一定数量的教条。在C语言中,以前担心诸如自修改代码或线程之类的问题,而在GC语言中,它是在强制GC或阻止其运行。
与大多数准则和良好的经验法则(以及良好的设计实践)一样,在极少数情况下,遵循既定准则确实有意义。您必须非常确定自己了解案件,您的案件确实需要废除惯例,并且您了解可能引起的风险和副作用。但是有这种情况。
编程问题千差万别,需要灵活的方法。我已经看到了用垃圾收集的语言阻止GC的情况,以及触发它而不是等待它自然发生的情况。在95%的时间中,这两种情况都表示没有正确解决问题。但是20中有1次可能有一个有效的理由。
不知道这是否是最佳实践,但是在循环处理大量图像时(即创建和布置许多Graphics / Image / Bitmap对象),我会定期使用GC.Collect。
我想我读到某个地方说,GC仅在程序(主要是)空闲时才运行,而不是在密集循环的中间运行,因此看起来好像是手动GC有意义的领域。
我最近遇到的一种情况是需要手动调用,这种情况GC.Collect()
是在处理包裹在小型托管C ++对象中的大型C ++对象时进行的,这些对象又可以从C#中进行访问。
垃圾回收器从未被调用,因为使用的托管内存量可以忽略不计,但是使用的非托管内存量却很大。手动调用Dispose()
对象要求我跟踪自己何时不再需要对象,而调用GC.Collect()
将清除不再引用的任何对象.....
GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)
在构造函数中调用,然后GC.RemoveMemoryPressure(addedSize)
在终结器中调用。这样,垃圾收集器将自动运行,并考虑到可以收集的非托管结构的大小。stackoverflow.com/questions/1149181/...
假设您的程序没有内存泄漏,对象积累并且无法在Gen 0中进行GC处理,因为:1)它们被长时间引用,因此请进入Gen1和Gen2;2)它们是大对象(> 80K),因此请进入LOH(大对象堆)。而且LOH不会像Gen0,Gen1和Gen2那样进行压缩。
检查“ .NET Memory”的性能计数器,是否可以看到1)问题确实不是问题。通常,每10个Gen0 GC将触发1个Gen1 GC,每10个Gen1 GC将触发1个Gen2 GC。从理论上讲,如果GC0上没有压力(如果程序内存使用确实已连接),则GC1和GC2将永远无法进行GC处理。我从来没有发生过。
对于问题2),您可以检查“ .NET内存”性能计数器以验证LOH是否变得肿。如果确实是您遇到的问题,那么可以按照此博客的建议创建一个大对象池,网址为http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx。
我想补充一点:调用GC.Collect()(+ WaitForPendingFinalizers())是故事的一部分。正如其他人正确提到的那样,GC.COllect()是非确定性集合,并且由GC本身(CLR)自行决定。即使您添加了对WaitForPendingFinalizers的调用,它也可能不确定。从此msdn 链接中获取代码,然后以对象循环迭代为1或2的方式运行代码。您将发现非确定性的含义(在对象的析构函数中设置断点)。确切地说,当Wait ..()仅存在1(或2)个挥之不去的对象时,不会调用析构函数。[Citation reqd。]
如果您的代码正在处理非托管资源(例如:外部文件句柄),则必须实现析构函数(或终结器)。
这是一个有趣的示例:
注意:如果您已经尝试过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()。这就是本质!:)
还有一件事,显式触发GC Collect可能不会提高程序的性能。很可能使情况变得更糟。
.NET GC经过精心设计和调整,可以自适应,这意味着它可以根据程序内存使用的“习惯”来调整GC0 / 1/2阈值。因此,经过一段时间的运行,它将适应您的程序。显式调用GC.Collect之后,阈值将被重置!.NET必须花费时间来再次适应程序的“习惯”。
我的建议是始终信任.NET GC。如果出现任何内存问题,请检查“ .NET内存”性能计数器并诊断我自己的代码。