什么时候可以调用 GC.Collect?

一般的建议是你不应该从你的代码调用GC.Collect,但是这个规则有什么例外呢?

我只能想到几个非常具体的情况,在这些情况下强制进行垃圾收集是有意义的。

我想到的一个例子是一个服务,它每隔一段时间就会醒来,执行一些任务,然后长时间地休眠。在这种情况下,强制进行收集可能是一个好主意,以防止即将空闲的进程占用超过所需的内存。

是否还有其他可以调用GC.Collect的情况?

136052 次浏览

看看Rico Mariani的这篇文章。他给出了两条何时调用GC的规则。收集(规则1:“不要”):

何时调用GC.Collect() . collect (

一种情况是当你试图单元测试使用WeakReference引用的代码。

在你的例子中,我认为调用GC。收集不是问题,而是设计问题。

如果你打算每隔一段时间醒来(设定时间),那么你的程序应该为一次执行而设计(执行一次任务),然后终止。然后,将程序设置为计划任务,以计划的间隔运行。

这样,您就不必关心调用GC了。收集,(如果有的话,你应该很少必须这样做)。

话虽如此,Rico Mariani在这个主题上有一篇很棒的博客文章,可以在这里找到:

http://blogs.msdn.com/ricom/archive/2004/11/29/271829.aspx

如果您有充分的理由相信一组重要的对象(特别是那些您怀疑属于第1代和第2代的对象)现在有资格进行垃圾收集,那么就性能影响较小而言,现在是收集的合适时机。

一个很好的例子是,如果您刚刚关闭了一个大型表单。您知道,现在所有的UI控件都可以被垃圾收集,当表单关闭时的短暂停顿可能不会引起用户的注意。

更新2.7.2018

从。net 4.5开始——有GCLatencyMode.LowLatencyGCLatencyMode.SustainedLowLatency。当进入和离开这些模式时,建议使用GC.Collect(2, GCCollectionMode.Forced)强制进行完整的GC。

从。net 4.6开始-有GC.TryStartNoGCRegion方法(用于设置只读值GCLatencyMode.NoGCRegion)。这本身可以执行完整的阻塞垃圾收集,试图释放足够的内存,但鉴于我们在一段时间内不允许GC,我认为在之前和之后执行完整的GC也是一个好主意。

来源:微软工程师本·沃森:编写高性能的。net代码,第二版。2018。

看到的:

简短的回答是:永远不会!

我只在编写粗糙的性能/分析器测试平台时使用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

这与问题没有太大关系,但是对于. net (xslcompiledtransform)中的XSLT转换,您可能别无选择。另一个候选是MSHTML控件。

当你知道一些垃圾收集器不知道的应用程序性质时,你可以调用GC.Collect()

作为作者,我们很容易认为这是可能的或正常的。然而,事实是GC是一个编写得很好、测试得很好的专家系统,您很少了解它所不知道的低级代码路径。

我能想到的最好的例子,你可能会有一些额外的信息是一个应用程序,在空闲时间和非常繁忙的时间之间循环。您希望在繁忙期间获得最佳性能,因此希望利用空闲时间进行一些清理工作。

然而,大多数时候GC足够聪明,无论如何都能做到这一点。

最佳实践是在大多数情况下不强制进行垃圾收集。(我工作过的每个系统都有强制垃圾收集,有突出的问题,如果解决了这些问题,就不需要强制垃圾收集,并大大加快了系统的速度。)

存在一个一些情况下,当比垃圾收集器更了解内存使用情况时。在多用户应用程序或一次响应多个请求的服务中不太可能出现这种情况。

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

  • 是否在命令行上给出一个文件名列表
  • 处理单个文件,然后将结果写入结果文件。
  • 在处理文件时,创建了许多相互链接的对象,直到文件处理完成(例如解析树)才能收集这些对象。
  • 不保持匹配状态之间的文件已处理

五月能够做出一个案例(经过仔细的)测试,你应该在处理每个文件后强制进行完整的垃圾收集。

另一种情况是服务每隔几分钟就会被唤醒来处理一些项,而当它睡着时不保持任何状态。那么在睡觉之前强制一个完整的收集五月是值得的。

我唯一会考虑强迫的时候 收藏就是当我知道很多的时候 的对象是最近创建的 目前很少有物体是这样的 引用。< / p >

我宁愿有一个垃圾收集API,因为我可以给它提示这类事情,而不必自己强制进行垃圾收集。

参见&;Rico Mariani的表演花絮"


这些天,我认为使用一个短寿命的工作进程来完成每批工作,让操作系统进行资源恢复会更好。

在大型的24/7或24/6系统(对消息、RPC请求或连续轮询数据库或进程作出反应的系统)中,有一种识别内存泄漏的方法是很有用的。为此,我倾向于向应用程序添加一种机制,暂时暂停任何处理,然后执行完整的垃圾收集。这会使系统处于静止状态,在这种状态下,剩余的内存要么是合法的长寿命内存(缓存、配置等),要么是“泄漏”的(不期望或不希望被根化但实际上是根化的对象)。

使用这种机制可以更容易地分析内存使用情况,因为报告不会受到活动处理的干扰。

为了确保你得到所有的垃圾,你需要执行两个收集:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

因为第一次收集将导致任何带有终结器的对象被终结(但实际上不会对这些对象进行垃圾收集)。第二个GC将对这些最终完成的对象进行垃圾收集。

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();
//
}

在某些情况下,谨慎总比后悔好。

这里有一种情况。

可以使用IL重写在c#中创建非托管 DLL(因为在某些情况下这是必要的)。

现在假设,例如,DLL在类级别创建了一个字节数组——因为许多导出的函数需要访问这样的数组。卸载DLL时会发生什么?这时垃圾回收器会自动被调用吗?我不知道,但是作为一个非托管 DLL,完全有可能不调用GC。如果它不被调用,那将是一个大问题。当DLL被卸载时,垃圾收集器也会被卸载——那么谁将负责收集任何可能的垃圾,他们将如何做呢?最好使用c#的垃圾回收器。有一个清理函数(对DLL客户端可用),其中类级变量被设置为null,并调用垃圾收集器。

小心总比后悔好。

我还是不太确定。 我在应用服务器上工作了7年。我们的大型安装使用24gb Ram。它是高度多线程的,对GC.Collect()的所有调用都遇到了非常糟糕的性能问题 许多第三方组件使用GC.Collect(),当他们认为现在这样做很聪明的时候。 所以简单的一堆Excel-Reports会在一分钟内阻塞应用服务器的所有线程几次

为了移除GC.Collect()调用,我们不得不重构所有的第三方组件,在这样做之后,所有组件都工作得很好。

但我也在Win32上运行服务器,在这里我开始大量使用GC.Collect()后得到一个OutOfMemoryException。

但我也很不确定这一点,因为我经常注意到,当我得到一个OOM上32位,我重试运行相同的操作,没有调用GC.Collect(),它只是工作得很好。

我想知道的一件事是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都增加了等待时间。)

调用GC.Collect()的一个有用的地方是在单元测试中,当您想要验证您没有创建内存泄漏时(例如,如果您正在使用WeakReferences或ConditionalWeakTable,动态生成的代码等)。

例如,我有几个测试:

WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);

可能有人认为使用WeakReferences本身就是一个问题,但似乎如果您正在创建一个依赖于这种行为的系统,那么调用GC.Collect()是验证这种代码的好方法。

作为内存碎片解决方案。 当我在内存流中写入大量数据(从网络流中读取)时,我正在退出内存异常。数据以8K块的形式写入。到达128M后出现了异常,尽管有很多可用的内存(但它是碎片化的)。调用GC.Collect()解决了这个问题。在修复后,我能够处理超过1G

您应该尽量避免使用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%

如果你使用的是。net 4.5以下的版本,手动收集可能是不可避免的(特别是当你处理许多“大对象”时)。

这个链接描述了原因:

https://blogs.msdn.microsoft.com/dotnet/2011/10/03/large-object-heap-improvements-in-net-4-5/

调用GC的一个很好的原因是在内存很小的小型ARM计算机上,比如树莓派(运行于单声道)。 如果未分配的内存片段使用了太多的系统RAM,那么Linux操作系统就会变得不稳定。 我有一个应用程序,我必须每秒钟调用GC(!)来摆脱内存溢出问题

另一个好的解决方案是在不再需要对象时释放对象。不幸的是,这在很多情况下并不容易。

几乎有必要调用GC.Collect()的一个实例是通过Interop自动化Microsoft Office。Office的COM对象不喜欢自动释放,这可能导致Office产品的实例占用大量内存。我不确定这是一个问题还是故意的。网上有很多关于这个话题的帖子,所以我就不赘述了。

当使用Interop编程时,每一个 COM对象应该手动释放,通常使用Marshal.ReleseComObject()。此外,手动调用垃圾收集可以帮助“清理”一点。当你处理完Interop对象后,调用下面的代码似乎会有很大帮助:

GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()

根据我个人的经验,结合使用ReleaseComObject和手动调用垃圾收集极大地可以减少Office产品的内存使用,特别是Excel。

我在数组和列表上做了一些性能测试:

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();

由于存在小对象堆(SOH)和大对象堆(LOH)

我们可以调用GC.Collect()来清除SOP中的去引用对象,并将活对象移动到下一代。

在。net4.5中,我们也可以使用largeobjectheapcompactionmode来压缩LOH

另一个原因是当你在USB COM端口上打开了SerialPort,然后USB设备被拔下。因为SerialPort已打开,所以该资源在系统注册表中保存了对先前连接的端口的引用。系统的注册表将会包含陈旧的数据,所以可用端口的列表将是错误的。因此端口必须关闭。

在端口上调用SerialPort.Close()会在对象上调用Dispose(),但它一直在内存中,直到垃圾收集实际运行,导致注册表一直处于stale状态,直到垃圾收集器决定释放资源。

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;

如果你正在创建很多新的System.Drawing.Bitmap对象,垃圾收集器不会清除它们。最终,GDI+会认为您的内存即将耗尽,并抛出“参数无效”异常。每隔一段时间调用GC.Collect()(不是太频繁!)似乎可以解决这个问题。