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


166

一般建议是,您不应GC.Collect从代码中调用,但是此规则的例外是什么?

我只能想到一些非常特殊的情况,在这些情况下强制进行垃圾回收是有意义的。

想到的一个示例是一项服务,该服务每隔一段时间醒来,执行一些任务,然后长时间睡眠。在这种情况下,最好强制执行一次收集以防止即将闲置的进程保留所需的更多内存。

还有其他情况下可以接受通话GC.Collect吗?


Answers:


153

如果您有充分的理由相信大量对象(尤其是您怀疑属于第1代和第2代的对象)现在可以进行垃圾收集了,那么就性能损失较小而言,现在是适当的收集时间。

一个很好的例子是,如果您刚刚关闭了一个大表格。您知道现在可以对所有UI控件进行垃圾回收,并且由于关闭表单而造成的短暂暂停对于用户而言可能不会引起注意。

更新2.7.2018

从.NET 4.5开始- GCLatencyMode.LowLatencyGCLatencyMode.SustainedLowLatency。进入和退出这两种模式时,建议您使用强制使用完整的GC GC.Collect(2, GCCollectionMode.Forced)

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

资料来源:微软工程师本·沃森(Ben Watson):编写高性能.NET代码,第二版。2018。

看到:


8
根据MS源代码,每850毫秒调用GC.Collect(2)就可以了。不相信吗 然后只需查看PresentationCore.dll,MS.Internal.MemoryPressure.ProcessAdd()。我目前有一个图像处理应用程序(小图像,没有任何真正的内存压力),其中调用GC.Collect(2)的时间超过850ms,因此整个应用程序因此而冻结(该应用程序在GC中花费了99.7%的时间)。
springy76 2011年

36
@ springy76:作为微软在一个地方完成,并不意味着它视为一件好事,那些提建议微软...
乔恩斯基特

4
我不喜欢那个例子。表格关闭后该怎么做?我看到的一个很好的例子是在XBox或WindowsPhone上加载游戏级别之后。在那些平台上,GC会在分配了1MB或更多内存后运行。因此最好在加载关卡时分配尽可能多的内存(同时显示一些初始屏幕),然后执行GC.Collect来尝试避免在游戏过程中发生收集。
霹雳霹雳州

8
@Peri:在表单关闭后执行此操作的要点是,您已经使一堆对象(控件,正在显示的数据)符合垃圾收集的条件-因此通过调用,GC.Collect您基本上是在告诉垃圾收集器您知道比改变更好。你为什么不喜欢这个例子?
乔恩·斯基特

6
@SHCJ:GC.Collect()将要求GC执行完整收集。如果您知道自己刚刚制作了许多可以进行垃圾收集的长期存在的对象,并且您相信用户现在不太可能注意到比现在稍稍暂停的时间,那么认为现在是更好的做法似乎是完全合理的。是时候提示收集而不是让它稍后发生。
乔恩·斯基特

50

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足以在此处发布的方法,例如sort-up),它显示了循环字符串串联和之间的区别StringBuilder

如果我需要一些精确的信息,那么这将是两个完全独立的测试-但是如果我们只想在测试过程中最小化(或标准化)GC以大致了解其行为,通常这就足够了。

在生产代码期间?我尚未使用它; -p


1
在这种情况下,我可能还会添加“ WaitForPendingFinalizers”(或其他内容);-p
Marc Gravell

29

最佳做法是在大多数情况下不要强制垃圾回收。 (我研究过的每个系统都强制执行垃圾收集,着重强调了以下问题:如果解决了这些问题,将消除了强制执行垃圾收集的需求,并大大加快了系统运行速度。)

在某些情况下比垃圾收集器更了解内存使用情况。在多用户应用程序或一次响应一个以上请求的服务中,这不太可能是正确的。

但是,在某些批处理中,您对GC的了解更多。例如考虑一个应用程序。

  • 在命令行上获得文件名列表
  • 处理单个文件,然后将结果写到结果文件中。
  • 在处理文件时,会创建很多互连的对象,这些对象在文件处理完成之前无法收集(例如,解析树)
  • 在已处理的文件之间不保持匹配状态

可能可以进行案例分析(经过仔细的测试),在处理完每个文件后应强制执行完整的垃圾回收。

另一种情况是,该服务每隔几分钟醒来以处理一些项目,并且在睡眠时不保持任何状态。然后在临睡前强制进行一次完整的收集可能是值得的。

我唯一考虑强制收集的时间是当我知道最近创建了很多对象并且当前仅引用了很少的对象时。

当我可以向我提供有关此类事情的提示而不必强迫自己使用GC时,我宁愿拥有一个垃圾收集API。

另请参阅“ Rico Mariani的演奏花絮



11

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

有了这种机制,就可以更轻松地分析内存使用情况,因为报表不会被活动处理产生的噪音所笼罩。

为确保获得所有垃圾,您需要执行两个收集:

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

因为第一个集合将使带有终结器的所有对象都被终结(但实际上不会垃圾收集这些对象)。第二个GC将垃圾收集这些最终对象。


现在,我已经在几个地方看到了两次遍历集合,但是在阅读了MSDN文档中有关GC.WaitForPendingFinalizers的文章之后,他说:“等待所有终结器完成,然后继续。没有对GC.WaitForPendingFinalizers的调用,下面的worker循环可能与终结器同时执行。通过此调用,worker循环仅在所有终结器都被调用后才执行。” 我只是一个偏执狂。您知道两次通过的确切来源吗?
jerhewet 2011年

1
@jerhewet:了解为什么需要两个集合的关键在于了解终结器对对象的处理方式。不幸的是,我没有您想要的东西,但是您已经阅读了本文以及关于SO的问题
Paul Ruane

10

当您了解垃圾收集器不了解的应用程序性质时,可以调用GC.Collect()。极有可能认为,作为作者,这很有可能。但是,事实是GC构成了一个相当好的编写和测试的专家系统,而且很少有人会了解它所不具备的低级代码路径。

我能想到的最好的示例是,您可以在空闲时段和非常繁忙的时段之间循环运行。您希望在繁忙的时段中获得最佳性能,因此想利用空闲时间进行一些清理。

但是,大多数情况下,GC足够聪明,无论如何都能做到这一点。


8

作为内存碎片解决方案。我在将大量数据写入内存流(从网络流读取)时摆脱了内存异常。数据以8K块写入。达到128M后,即使有很多可用内存,也有例外(但它是零散的)。调用GC.Collect()解决了该问题。修复后,我能够处理超过1G的数据。


7

看看Rico Mariani的这篇文章。当调用GC.Collect时,他给出了两个规则(规则1是:“不要”):

何时调用GC.Collect()


3
已经去过那里了。我并不想为做不该做的事情找借口,但我想知道是否有任何特定的情况可以接受。
Brian Rasmussen

5

通过Interop自动化Microsoft Office时,几乎需要调用GC.Collect()的一个实例。Office的COM对象不喜欢自动释放,并且可能导致Office产品实例占用大量内存。我不确定这是问题还是设计使然。互联网上有很多关于此主题的文章,因此我不会赘述。

使用Interop进行编程时,通常应通过使用Marshal.ReleseComObject()来手动释放每个 COM对象。另外,手动调用垃圾回收可以帮助“清理”。使用完Interop对象后,调用以下代码似乎会有所帮助:

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

以我个人的经验,结合使用ReleaseComObject和手动调用垃圾回收会大大减少Office产品(特别是Excel)的内存使用量。


是的,我遇到了.net访问excel的问题,它也可以通过com对象工作。重要的是要注意,在DEBUG模式下,该方法不能很好地工作,因为那里的GC操作受到限制。仅在“释放”模式下可以正常工作。相关链接:stackoverflow.com/questions/17130382/…–
Blechdose

5

我正在对数组和列表进行一些性能测试:

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

并且我进入OutOfMemoryExceptionGetSomeNumbers_Enumerable_Range方法,唯一的解决方法是通过以下方式取消分配内存:

numbers = null;
GC.Collect();

为什么拒绝投票?我的答案是一个演示何时调用GC的示例。您有更好的建议吗?欢迎您提出。
Daniel B

4

在您的示例中,我认为调用GC.Collect不是问题,而是存在设计问题。

如果您要间隔一定的时间(设置时间)醒来,则应将程序设计为执行一次(执行一次任务),然后终止。然后,将程序设置为预定任务,以预定间隔运行。

这样,您不必担心调用GC.Collect(这是您应该很少要做的事情)。

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

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


3

当您要验证自己没有造成内存泄漏时(例如,如果您正在使用WeakReferences或ConditionalWeakTable进行某些操作,动态生成的代码等),则在单元测试中可以调用GC.Collect()的一个有用的地方。

例如,我有一些测试,例如:

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

可以说使用WeakReferences本身就是一个问题,但是似乎如果您要创建一个依赖这种行为的系统,则调用GC.Collect()是验证此类代码的好方法。




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

2

在某些情况下,安全比后悔好。

这是一种情况。

可以使用IL重写在C#中编写非托管 DLL(因为在某些情况下有必要这样做)。

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

安全胜过遗憾。


2

我对此还不确定。我在应用服务器上工作了7年。我们更大的安装使用24 GB Ram。它的高度多线程和所有对GC.Collect()的调用都遇到了非常可怕的性能问题。

当许多第三方组件认为现在明智的做法是使用GC.Collect()时。因此,一堆简单的Excel报表在一分钟内几次阻塞了所有线程的App Server。

为了删除GC.Collect()调用,我们必须重构所有第3方组件,并且所有组件在此之后都可以正常工作。

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

但是我对此也不太确定,因为我经常注意到,当我在32位上获得OOM时,我再次尝试运行相同的操作,而没有调用GC.Collect(),它运行良好。

我想知道的一件事是OOM异常本身...如果我已经编写了.Net Framework,并且无法分配内存块,则将使用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。由于它具有广泛的应用程序影响,因此只有应用程序才能这样做(如果有的话)。
CodesInChaos 2013年

If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(),-我认为他们已经在这样做-我已经看到迹象表明内部GC触发器之一是分配内存的某些失败。
G. Stoynev

2

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


1

另一个原因是当您在USB COM端口上打开了SerialPort,然后拔下了USB设备。由于打开了SerialPort,因此资源在系统注册表中保留了对先前连接的端口的引用。然后,系统的注册表将包含过时的数据,因此可用端口的列表将是错误的。因此,必须关闭端口。

在端口上调用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;

0

这与问题无关,但是对于.NET(XSLCompiledTranform)中的XSLT转换,您可能别无选择。另一个候选对象是MSHTML控件。



0

调用GC的一个好理由是在内存很少的小型ARM计算机上,例如Raspberry PI(与mono运行)。如果未分配的内存碎片占用过多的系统RAM,则Linux OS可能会变得不稳定。我有一个应用程序,我必须每秒调用一次GC(!)来消除内存溢出问题。

另一个好的解决方案是在不再需要对象时对其进行处置。不幸的是,这在许多情况下都不是那么容易。


0

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

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

在.net4.5中,我们还可以通过使用largeobjectheapcompactionmode来压缩LOH


0

如果要创建很多新System.Drawing.Bitmap对象,则垃圾收集器不会清除它们。最终,GDI +会认为您内存不足,并会引发“参数无效”异常。GC.Collect()如此频繁地呼叫(不是太频繁!)似乎可以解决此问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.