一般建议您不应该从代码中调用GC.Collect
,但此规则的例外情况是什么?
我只能想到一些非常具体的案例,强制垃圾收集可能是有意义的。
一个让人想起的例子是服务,它会间歇醒来,执行一些任务,然后长时间睡眠。在这种情况下,强制收集以防止即将空闲的进程保持比需要的更多内存可能是个好主意。
是否有其他情况可以接听GC.Collect
?
答案 0 :(得分:144)
如果您有充分的理由相信一组重要的物品 - 特别是那些您怀疑属于第1代和第2代的物体 - 现在有资格进行垃圾收集,那么现在可以根据小的表现。
一个很好的例子就是你刚刚关闭了一个大表格。您知道所有UI控件现在都可以进行垃圾回收,并且表单关闭时非常短暂的停顿可能不会让用户注意到。
更新2.7.2018
从.NET 4.5开始 - 有GCLatencyMode.LowLatency
和GCLatencyMode.SustainedLowLatency
。当进入和离开这些模式中的任何一种时,建议您使用GC.Collect(2, GCCollectionMode.Forced)
强制使用完整的GC。
从.NET 4.6开始 - 有GC.TryStartNoGCRegion
方法(用于设置只读值GCLatencyMode.NoGCRegion
)。这本身可以执行完全阻塞垃圾收集以试图释放足够的内存,但鉴于我们不允许使用GC一段时间,我认为在之前和之后执行完整的GC也是一个好主意。
来源:微软工程师Ben Watson:编写高性能.NET代码,第二版。 2018。
请参阅:
答案 1 :(得分:47)
我只在编写原始性能/分析器测试装置时才使用GC.Collect
;即我有两个(或更多)代码块来测试 - 例如:
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
离开临界点而受到重创
一个典型的例子是一个简单的控制台exe(一个Main
方法排序 - 足以在这里发布),它显示了循环字符串连接和StringBuilder
之间的区别。
如果我需要精确的东西,那么这将是两个完全独立的测试 - 但是如果我们只想在测试期间最小化(或标准化)GC以获得对行为的粗略感觉,这通常就足够了。
在生产代码期间?我还没有使用它;-p
答案 2 :(得分:29)
最佳做法是在大多数情况下不强制进行垃圾收集。(我曾经处理的每个系统都强制进行垃圾收集,强调了问题,如果解决这个问题就会消除强迫的需要垃圾收集,大大加快了系统。)
当你了解有关内存使用情况的更多内容时,少数情况然后垃圾收集器会这样做。在多用户应用程序或一次响应多个请求的服务中,这种情况不太可能发生。
然而,在某些批处理类型处理中,您确实了解GC。例如。考虑一个应用程序。
另一种情况是每隔几分钟醒来处理一些项目的服务,在睡着时不保持任何状态。然后在睡觉之前强制完整收集可能值得。
我唯一一次考虑强迫 一个集合就是我知道的很多 最近创建了对象 目前很少有物品 引用。
我宁愿拥有一个垃圾收集API,当我能给它提示这种类型的东西,而不必强迫GC自己。
答案 3 :(得分:18)
有一种情况是您尝试使用WeakReference单元测试代码。
答案 4 :(得分:9)
在大型24/7或24/6系统中 - 对消息,RPC请求或连续轮询数据库或进程的系统做出反应 - 有一种识别内存泄漏的方法很有用。为此,我倾向于为应用程序添加一种机制,以暂时挂起任何处理,然后执行完整的垃圾收集。这使系统处于静止状态,其中剩余的存储器是合法的长寿命存储器(高速缓存,配置等)或者是“泄漏的”(不期望或希望被植根但实际上是对象的对象)。
使用此机制可以更轻松地分析内存使用情况,因为报告不会受到活动处理噪音的影响。
为了确保获得所有垃圾,您需要执行两个集合:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
由于第一个集合将导致任何具有终结器的对象被最终化(但实际上并不是垃圾收集这些对象)。第二个GC将垃圾收集这些最终的对象。
答案 5 :(得分:8)
作为内存碎片解决方案。 在将大量数据写入内存流(从网络流中读取)时,我遇到了内存不足的问题。数据以8K块的形式写成。达到128M后,即使有大量可用内存(但它是碎片化的),也有异常。调用GC.Collect()解决了这个问题。修复后我能够处理超过1G。
答案 6 :(得分:8)
当您了解垃圾收集器不具备应用程序性质的某些内容时,您可以调用GC.Collect()。很有可能认为,作为作者,这很有可能。然而,事实是GC相当于一个写得很好并且经过测试的专家系统,而且你很少知道它没有的低级代码路径。
我能想到你可能有一些额外信息的最佳例子是一个在空闲时段和非常繁忙的时段之间循环的应用程序。您希望在繁忙时段获得最佳性能,因此希望使用空闲时间进行一些清理。
然而,大部分时间GC都足够聪明,无论如何都要这样做。
答案 7 :(得分:7)
看看Rico Mariani撰写的这篇文章。他给出了两个规则来调用GC.Collect(规则1是:“不要”):
答案 8 :(得分:5)
我正在对数组和列表进行一些性能测试:
Error in lemma_hashmap[[tokens]] :
attempt to select more than one element in vectorIndex
我在GetSomeNumbers_Enumerable_Range方法中获得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));
}
唯一的解决方法是通过以下方式释放内存:
OutOfMemoryException
答案 9 :(得分:4)
在您的示例中,我认为调用GC.Collect不是问题,而是存在设计问题。
如果你要按时间间隔醒来(设定时间),那么你的程序应该为一次执行(执行任务一次)而终止。然后,将程序设置为计划任务,以按计划的时间间隔运行。
通过这种方式,你不必担心调用GC.Collect(你应该很少,如果有的话)。
话虽如此,Rico Mariani在这个主题上有一篇很棒的博文,可以在这里找到:
答案 10 :(得分:4)
几乎需要调用GC.Collect()的一个实例是通过Interop自动化Microsoft Office。 Office的COM对象不喜欢自动释放,并可能导致Office产品的实例占用大量内存。我不确定这是一个问题还是设计问题。互联网上有很多关于这个主题的帖子,所以我不会详细介绍。
使用Interop进行编程时,应该手动释放每个 COM对象,通常使用Marshal.ReleseComObject()。此外,手动调用垃圾收集可以帮助“清理”一点。完成Interop对象后调用以下代码似乎有所帮助:
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
根据我的个人经验,结合使用ReleaseComObject并手动调用垃圾收集 可以减少Office产品(特别是Excel)的内存使用量。
答案 11 :(得分:3)
当你想验证你没有创建内存泄漏时(如果你正在使用WeakReferences或ConditionalWeakTable,动态生成的代码等),调用GC.Collect()的一个有用的地方是单元测试。
例如,我有一些测试,如:
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
可以说使用WeakReferences本身就是一个问题,但似乎如果你正在创建一个依赖于这种行为的系统,那么调用GC.Collect()是一种验证这种代码的好方法。 / p>
答案 12 :(得分:2)
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();
//
}
答案 13 :(得分:2)
我仍然不确定这一点。 我在Application Server上工作了7年。我们更大的安装使用24 GB Ram。它的高度多线程,以及对GC.Collect()的所有调用遇到了非常糟糕的性能问题。
许多第三方组件在他们认为现在很聪明的时候使用了GC.Collect()。 因此,一堆简单的Excel报告每分钟几次阻止所有线程的App Server。
我们不得不重构所有第三方组件以删除GC.Collect()调用,并且在执行此操作后一切正常。
但我也在Win32上运行服务器,在这里我开始在获取OutOfMemoryException之后大量使用GC.Collect()。
但是我也很不确定这个,因为我经常注意到,当我在32位上获得一个OOM,并且我再次尝试再次运行相同的操作而不调用GC.Collect()时,它运行正常。 / p>
我想知道的一件事是OOM异常本身...... 如果我会编写.Net框架,并且我无法分配内存块,我会使用GC.Collect(),碎片整理内存(??),再试一次,如果我仍然无法找到一个空闲内存块,然后我会抛出OOM-Exception。
或者至少将此行为作为可配置选项,因为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特定行为,因为我们正在运行ORM缓存服务,如果RAM超过某些预定义值,则该服务需要一些时间来释放所有缓存的对象。它在第一次等待几秒钟,并且每次出现OOM都会增加等待时间。)
答案 14 :(得分:2)
在某些情况下,它比抱歉更安全。
这是一种情况。
可以使用IL重写在C#中创建非托管 DLL(因为有些情况需要这样做)。
现在假设,例如,DLL在类级别创建一个字节数组 - 因为许多导出的函数需要访问它们。卸载DLL时会发生什么?是否自动调用了垃圾收集器?我不知道,但作为非托管 DLL,完全有可能不会调用GC。如果没有召唤它将是一个大问题。当DLL被卸载时,垃圾收集器也是如此 - 所以谁将负责收集任何可能的垃圾,他们将如何做?最好使用C#的垃圾收集器。有一个清理函数(可用于DLL客户端),其中类级别变量设置为null并且垃圾收集器被调用。
比抱歉更安全。
答案 15 :(得分:2)
简短的回答是:永远不会!
答案 16 :(得分:2)
Scott Holden的blog entry on when to (and when not to) call GC.Collect特定于.NET Compact Framework,但规则通常适用于所有托管开发。
答案 17 :(得分:1)
你应该尽量避免使用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
}
测试结果: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();
}
测试结果:CPU使用率为2-3%
答案 18 :(得分:0)
这与问题无关,但对于.NET(XSLCompiledTranform)中的XSLT转换,您可能别无选择。另一个候选者是MSHTML控件。
答案 19 :(得分:0)
调用GC的一个很好的理由是在内存很少的小型ARM计算机上,比如Raspberry PI(使用单声道运行)。 如果未分配的内存碎片使用过多的系统RAM,那么Linux操作系统可能会变得不稳定。 我有一个应用程序,我必须每秒调用GC(!)以摆脱内存溢出问题。
另一个好的解决方案是在不再需要时处理对象。不幸的是,在许多情况下这并不容易。
答案 20 :(得分:0)
由于存在小对象堆(SOH)和大对象堆(LOH)
我们可以调用GC.Collect()来清除SOP中的de-reference对象,并将live object移动到下一代。
在.net4.5中,我们也可以使用largeobjectheapcompactionmode
来压缩LOH答案 21 :(得分:0)
另一个原因是,当您在USB COM端口上打开了SerialPort,然后拔下了USB设备时。由于SerialPort已打开,因此资源在系统注册表中保留了对先前连接的端口的引用。然后,系统的注册表将contain stale data,因此可用端口列表将是错误的。因此必须关闭端口。
在端口上调用SerialPort.Close()会在对象上调用Dispose(),但是它将一直保留在内存中,直到垃圾回收实际运行为止,从而导致注册表保持陈旧状态,直到垃圾回收器决定释放资源为止。
来自https://stackoverflow.com/a/58810699/8685342:
try
{
if (port != null)
port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
}
port = null;
答案 22 :(得分:0)
如果要创建许多新的System.Drawing.Bitmap
对象,则垃圾收集器不会清除它们。最终,GDI +会认为您的内存不足,并会引发“参数无效”异常。经常(不太经常!)调用GC.Collect()
似乎可以解决此问题。