什么时候可以调用GC.Collect?
一般的build议是,你不应该从你的代码中调用GC.Collect
,但是这个规则有什么例外?
我只能想到一些非常具体的情况下,强制垃圾收集是有意义的。
一个令人想起的例子是一个服务,它会间歇性地唤醒,执行一些任务,然后长时间睡眠。 在这种情况下,强制收集可能是一个不错的主意,以防止即将进行的闲置过程持续到所需的更多内存。
有没有其他的情况下可以调用GC.Collect
?
如果你有充分的理由相信一组重要的对象 – 特别是那些你怀疑是第一代和第二代的对象 – 现在有资格进行垃圾回收,那现在就是一个合适的时间来收集小的性能命中。
一个很好的例子就是如果你刚刚closures了一个大的表单。 你知道所有的UI控件现在都可以被垃圾回收了,而当表单closures的时候很短的停顿可能不会被用户注意到。
我只在使用GC.Collect
编写粗糙的性能/分析器testing平台; 即我有两个(或更多)代码块来testing – 如下所示:
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); TestA(); // may allocate lots of transient objects GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); TestB(); // may allocate lots of transient objects GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced); ...
所以TestA()
和TestB()
运行时的状态尽可能相似 – 也就是说, TestB()
不会因为TestA
离开临界点而TestA
。
一个经典的例子是一个简单的控制台exe(一个Main
方法sorting – 足以在这里例如张贴),显示循环string连接和StringBuilder
之间的区别。
如果我需要精确的东西,那么这将是两个完全独立的testing – 但是如果我们只是想在testing过程中最小化(或正常化)GC,以便粗略地感觉到这种行为,这就足够了。
在生产代码? 我还没有使用它;-p
最佳实践是在大多数情况下不强制垃圾收集。 (我所从事的每一个系统都强制垃圾收集,强调的问题是,如果解决了这个问题,就不需要强制垃圾收集,并且使系统大大加速。)
有几种情况,当你知道更多的内存使用情况,然后垃圾收集器。 在多用户应用程序或者一次响应多于一个请求的服务中,这不太可能是真实的。
但是在某些批处理types的处理中,您确实知道更多的GC。 例如考虑一个应用程序。
- 在命令行中给出了一个文件名列表
- 处理单个文件,然后将结果写入结果文件。
- 在处理文件时,创build了很多相互关联的对象,直到文件处理完成(如分析树)才能被收集,
- 在处理的文件之间不保持匹配状态 。
你可能会(经过仔细的)testing,你应该强制一个完整的垃圾回收后处理每个文件。
另一种情况是每隔几分钟醒来处理一些物品的服务,在睡眠时不保持任何状态 。 然后在睡觉之前强制收集完整的东西可能是值得的。
我只考虑强制一个集合的时候,是因为我知道最近有很多对象被创build,并且很less有对象被引用。
我宁愿有一个垃圾收集API,当我可以给它暗示这种types的事情,而不必强迫我自己的GC。
另见“ Rico Mariani的表演花絮 ”
一种情况是,当您尝试对使用WeakReference的代码进行unit testing时。
在大型24/7或24/6系统中 – 对消息,RPC请求或轮询数据库或进程的系统持续不断 – 有一个办法来识别内存泄漏。 为此,我倾向于为应用程序添加一个机制来临时暂停任何处理,然后执行完整的垃圾回收。 这使得系统进入静止状态,其中剩余的存储器是合法的长时间存储器(caching,configuration等),或者是“泄漏的”(不是预期的或期望被植入但实际上是对象的)。
使用这种机制可以简化内存使用情况,因为报告不会被活动处理中的噪音所笼罩。
为了确保你得到所有的垃圾,你需要执行两个集合:
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
因为第一个集合会导致任何带有终结器的对象被终结(但实际上并不是真正的垃圾收集这些对象)。 第二个GC将垃圾收集这些最终的对象。
当你知道垃圾收集器没有的应用程序的性质时,可以调用GC.Collect()。 认为这是很有诱惑力的,就像作者一样,这很可能。 然而事实是,GC相当于一个经过精心编写和testing的专家系统,而且很less有人知道它所不具备的低级代码path。
我能想到的最好的例子是你可能有一些额外的信息是一个应用程序,在空闲期间和非常繁忙的时期之间循环。 您希望在非常繁忙的时期获得最佳性能,因此希望利用空闲时间进行清理。
然而,大多数情况下GC仍然足够聪明,可以做到这一点。
看看Rico Mariani的这篇文章。 他给出了两个规则,当调用GC.Collect(规则1是:“不”):
何时调用GC.Collect()
作为内存碎片解决scheme。 将大量数据写入内存stream(从networkingstream中读取)时,我的内存出现exception。 数据是用8K块写的。 在达到128M之后,即使有很多可用的内存(但被分割),也是有例外的。 调用GC.Collect()解决了这个问题。 解决之后,我能够处理1G以上。
在你的例子中,我认为调用GC.Collect不是问题,而是一个devise问题。
如果你要间隔地唤醒(设置时间),那么你的程序应该为一次执行而制作(执行一次任务),然后终止。 然后,将程序设置为按预定时间间隔运行的计划任务。
这样,你不必关心调用GC.Collect,(你应该很less这么做,必须这样做)。
话虽如此,Rico Mariani在这个主题上有很棒的博客文章,可以在这里find:
调用GC.Collect()的一个有用的地方是当你想validation你没有创build内存泄漏(例如,如果你正在使用WeakReferences或者ConditionalWeakTable,dynamic生成的代码等等)时,它就在unit testing中。
例如,我有几个testing,如:
WeakReference w = CodeThatShouldNotMemoryLeak(); Assert.IsTrue(w.IsAlive); GC.Collect(); GC.WaitForPendingFinalizers(); Assert.IsFalse(w.IsAlive);
可以认为,使用WeakReference本身就是一个问题,但是如果你正在创build一个依赖于这种行为的系统,那么调用GC.Collect()是validation这种代码的好方法。
简短的回答是:从来没有!
Scott Holden 关于何时(以及何时不要)调用GC.Collect的博客条目是特定于.NET Compact Framework的 ,但是这些规则通常适用于所有可pipe理的开发。
我对此仍然很不确定。 我在一个应用服务器上工作了7年。 我们更大的安装使用24 GB内存。 它的hightly Multithreaded,所有GC.Collect()调用遇到了非常可怕的性能问题。
许多第三方组件使用GC.Collect()时,他们认为这是现在很聪明。 因此,一组简单的Excel报告一分钟内几次阻塞了所有线程的App Server。
我们不得不重构所有的第三方组件,以删除GC.Collect()调用,所有这一切都工作正常。
但是我也在Win32上运行服务器,在这里我开始大量使用GC.Collect()后得到一个OutOfMemoryException。
但我也很不确定,因为我经常注意到,当我得到一个32位的OOM,我再次运行相同的操作,没有调用GC.Collect(),它只是工作正常。
有一件事我想知道的是OOMexception本身…如果我会写.Net框架,我不能分配一个内存块,我会使用GC.Collect(),磁盘碎片整理内存(??),再试一次,如果我仍然不能find一个空闲的内存块,那么我会抛出OOMexception。
或者至less使这种行为作为可configuration的选项,由于GC.Collect性能问题的缺点。
现在我有很多像这样的代码在我的应用程序“解决”的问题:
public static TResult ExecuteOOMAware<T1, T2, TResult>(Func<T1,T2 ,TResult> func, T1 a1, T2 a2) { int oomCounter = 0; int maxOOMRetries = 10; do { try { return func(a1, a2); } catch (OutOfMemoryException) { oomCounter++; if (maxOOMRetries > 10) { throw; } else { Log.Info("OutOfMemory-Exception caught, Trying to fix. Counter: " + oomCounter.ToString()); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(oomCounter * 10)); GC.Collect(); } } } while (oomCounter < maxOOMRetries); // never gets hitted. return default(TResult); }
(请注意,Thread.Sleep()行为实际上是App特有的行为,因为我们正在运行ORMcaching服务,并且如果RAM超过某些预定义的值,服务需要一些时间才能释放所有caching的对象,因此它会等待几秒钟的第一次,并增加了每个OOM发生的等待时间。
using(var stream = new MemoryStream()) { bitmap.Save(stream, ImageFormat.Png); techObject.Last().Image = Image.FromStream(stream); bitmap.Dispose(); // Without this code, I had an OutOfMemory exception. GC.Collect(); GC.WaitForPendingFinalizers(); // }
在某些情况下,安全性比抱歉的要好。
这是一种情况。
有可能在C#中使用IL重写来创build非托pipe DLL(因为在某些情况下,这是必要的)。
现在假设,例如,DLL在类级别创build一个字节数组 – 因为许多导出的函数都需要访问这样的数据。 当DLL被卸载时会发生什么? 那个垃圾收集器是否自动调用? 我不知道,但作为一个非托pipe的 DLL,完全有可能没有调用GC。 如果不叫,这将是一个大问题。 当DLL被卸载的时候,垃圾收集器也是如此 – 那么谁来负责收集任何可能的垃圾,他们将如何做? 最好使用C#的垃圾收集器。 有一个清理函数(可用于DLL客户端)的类级别variables设置为null和垃圾收集器调用。
比对不起更安全。
你应该尽量避免使用GC.Collect(),因为它非常昂贵。 这里是一个例子:
public void ClearFrame(ulong timeStamp) { if (RecordSet.Count <= 0) return; if (Limit == false) { var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000; if (seconds <= _preFramesTime) return; Limit = true; do { RecordSet.Remove(RecordSet[0]); } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime); } else { RecordSet.Remove(RecordSet[0]); } GC.Collect(); // AVOID }
testing结果:CPU使用率12%
当你改变这个:
public void ClearFrame(ulong timeStamp) { if (RecordSet.Count <= 0) return; if (Limit == false) { var seconds = (timeStamp - RecordSet[0].TimeStamp)/1000; if (seconds <= _preFramesTime) return; Limit = true; do { RecordSet[0].Dispose(); // Bitmap destroyed! RecordSet.Remove(RecordSet[0]); } while (((timeStamp - RecordSet[0].TimeStamp) / 1000) > _preFramesTime); } else { RecordSet[0].Dispose(); // Bitmap destroyed! RecordSet.Remove(RecordSet[0]); } //GC.Collect(); }
testing结果:CPU使用率2-3%
一个几乎需要调用GC.Collect()的实例是通过Interop自动化Microsoft Office的时候。 Office的COM对象不喜欢自动发布,并可能导致Office产品的实例占用大量的内存。 我不确定这是一个问题还是devise。 在互联网上有很多关于这个话题的文章,所以我不会详细介绍。
使用Interop进行编程时,应该手动释放每个 COM对象,通常通过使用Marshal.ReleseComObject()。 另外,手动调用Garbage Collection可以帮助“清理”一下。 当您完成Interop对象时,调用以下代码似乎有点帮助:
GC.Collect() GC.WaitForPendingFinalizers() GC.Collect()
以我的个人经验,使用ReleaseComObject和手动调用垃圾回收的组合大大减less了Office产品(特别是Excel)的内存使用量。
这与问题没有关系,但是对于.NET(XSLCompiledTranform)中的XSLT转换,则可能别无select。 另一个候选人是MSHTML控件。
如果您使用的是小于4.5的.net版本,手动收集可能是不可避免的(特别是在处理许多“大型对象”时)。
此链接描述了为什么:
https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/
调用GC的一个很好的理由是在内存很小的小型ARM计算机上,如Raspberry PI(使用单声道运行)。 如果未分配的内存碎片使用太多的系统RAM,则Linux操作系统可能会变得不稳定。 我有一个应用程序,我必须每秒调用GC(!)来摆脱内存溢出问题。
另一个好的解决方法是在不再需要的时候处理对象。 不幸的是,这在许多情况下并不那么容易。
我在数组和列表上做了一些性能testing:
private static int count = 100000000; private static List<int> GetSomeNumbers_List_int() { var lstNumbers = new List<int>(); for(var i = 1; i <= count; i++) { lstNumbers.Add(i); } return lstNumbers; } private static int[] GetSomeNumbers_Array() { var lstNumbers = new int[count]; for (var i = 1; i <= count; i++) { lstNumbers[i-1] = i + 1; } return lstNumbers; } private static int[] GetSomeNumbers_Enumerable_Range() { return Enumerable.Range(1, count).ToArray(); } static void performance_100_Million() { var sw = new Stopwatch(); sw.Start(); var numbers1 = GetSomeNumbers_List_int(); sw.Stop(); //numbers1 = null; //GC.Collect(); Console.WriteLine(String.Format("\"List<int>\" took {0} milliseconds", sw.ElapsedMilliseconds)); sw.Reset(); sw.Start(); var numbers2 = GetSomeNumbers_Array(); sw.Stop(); //numbers2 = null; //GC.Collect(); Console.WriteLine(String.Format("\"int[]\" took {0} milliseconds", sw.ElapsedMilliseconds)); sw.Reset(); sw.Start(); //getting System.OutOfMemoryException in GetSomeNumbers_Enumerable_Range method var numbers3 = GetSomeNumbers_Enumerable_Range(); sw.Stop(); //numbers3 = null; //GC.Collect(); Console.WriteLine(String.Format("\"int[]\" Enumerable.Range took {0} milliseconds", sw.ElapsedMilliseconds)); }
我在GetSomeNumbers_Enumerable_Range方法中得到OutOfMemoryException
唯一的解决方法是通过以下操作释放内存:
numbers = null; GC.Collect();