ChatGPT解决这个技术问题 Extra ChatGPT

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

一般建议是您不应该从代码中调用 GC.Collect,但该规则有哪些例外情况?

我只能想到一些非常具体的情况,强制垃圾收集可能是有意义的。

一个浮现在脑海中的例子是一项服务,它每隔一段时间就会醒来,执行一些任务,然后长时间休眠。在这种情况下,强制收集以防止即将空闲的进程占用比需要更多的内存可能是一个好主意。

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


B
Ben Hall

如果您有充分的理由相信一组重要的对象(尤其是您怀疑属于第 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 也是一个好主意。

资料来源:Microsoft 工程师 Ben Watson 的:编写高性能 .NET 代码,第 2 版。 2018 年。

看:

https://msdn.microsoft.com/en-us/library/system.runtime.gclatencymode(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/dn906204(v=vs.110).aspx


根据 MS 源代码,每 850 毫秒调用一次 GC.Collect(2) 就可以了。不相信?然后只需查看 PresentationCore.dll、MS.Internal.MemoryPressure.ProcessAdd()。我目前有一个图像处理应用程序(小图像,没有真正的内存压力),其中调用 GC.Collect(2) 花费的时间超过 850 毫秒,因此整个应用程序被冻结(应用程序在 GC 中花费了 99.7% 的时间)。
@springy76:由微软在一个地方完成并不意味着微软提供建议的人认为这是一件好事......
我不喜欢那个例子。表格关闭后这样做的目的是什么?我看到的一个很好的例子是在 Xbox 或 WindowsPhone 上加载游戏关卡之后。在那些平台上,GC 在分配 1MB 或类似的东西后运行。所以最好在加载关卡期间尽可能多地分配(同时显示一些启动画面),然后执行 GC.Collect 以尽量避免在游戏期间收集。
@Peri:在表单关闭后执行此操作的目的是您刚刚使一堆对象(控件,您正在显示的数据)符合垃圾收集的条件 - 所以通过调用 GC.Collect 您基本上是在告诉垃圾收集器你比它更了解改变。你为什么不喜欢这个例子?
@SHCJ:GC.Collect() 将请求 GC 执行 full 收集。如果您知道您刚刚使许多以前长期存在的对象符合垃圾回收的条件并且您认为用户现在不太可能注意到轻微的暂停而不是稍后,这似乎完全合理认为现在是提示收集的更好时机,而不是让它稍后发生。
V
Vlad Schnakovszki

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


在这种情况下,我可能也会添加“WaitForPendingFinalizers”(或其他任何内容);-p
I
Ian Ringrose

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

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

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

在命令行上给出文件名列表

处理单个文件,然后将结果写入结果文件。

在处理文件时,会创建很多相互关联的对象,直到文件处理完成(例如解析树)才能收集这些对象

在已处理的文件之间不保持匹配状态。

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

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

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

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

另请参阅“Rico Mariani's Performance Tidbits

这些天我认为上述相同的情况最好使用一个短暂的工作进程来完成每批工作并让操作系统进行资源恢复。


B
Brian

一种情况是您尝试对使用 WeakReference 的代码进行单元测试。


P
Paul Ruane

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

拥有这种机制可以更轻松地分析内存使用情况,因为报告不会被来自主动处理的噪音所笼罩。

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

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

因为第一次收集将导致任何具有终结器的对象被终结(但实际上不是垃圾收集这些对象)。第二次 GC 将垃圾收集这些最终对象。


我现在已经在几个地方看到了两遍集合,但是在阅读了 GC.WaitForPendingFinalizers 的 MSDN 文档中的段落之后,它说:“等待所有终结器完成后再继续。没有对 GC.WaitForPendingFinalizers 的调用,下面的工作循环可能与终结器同时执行。通过此调用,工作循环仅在调用所有终结器后执行我只是一个触摸偏执狂。你知道做两次传球的确切来源吗?
@jerhewet:理解为什么需要两个集合的关键是理解带有终结器的对象会发生什么。不幸的是,我没有您要的确切内容,但请阅读 this articlethis question on SO
J
Joel Coehoorn

当您了解垃圾收集器不了解的应用程序的性质时,您可以调用 GC.Collect()

作为作者,通常很容易认为这是可能的或正常的。然而,事实是 GC 相当于一个编写良好且经过测试的专家系统,您很少了解它所不了解的低级代码路径。

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

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


E
Ethos

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


我相信这个案例已经通过 .Net GC 系统的更新解决了。
g
garthhh

几乎需要调用 GC.Collect() 的一种情况是通过 Interop 自动化 Microsoft Office。 Office 的 COM 对象不喜欢自动释放,可能会导致 Office 产品的实例占用大量内存。我不确定这是一个问题还是设计使然。网上有很多关于这个话题的帖子,所以我不会太详细。

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

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

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


是的,我通过 .net 访问 excel 遇到了这个问题,它也可以通过 com 对象工作。需要注意的是,这在 DEBUG 模式下无法正常工作,因为 GC 操作在此受限。它将仅在 RELEASE 模式下按预期工作。相关链接:stackoverflow.com/questions/17130382/…
M
M4N

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

When to call GC.Collect()


已经去过那里了。我不是想为做你不应该做的事情找借口,但我想知道是否有任何具体情况可以接受。
D
Daniel B

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

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

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

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


c
casperOne

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

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

这样,您就不必担心调用 GC.Collect(如果有的话,您应该很少这样做)。

话虽如此,Rico Mariani 有一篇关于这个主题的精彩博文,可以在这里找到:

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


C
ChaseMedallion

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

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

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

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


B
Brandon Barkley

Scott Holden 的 blog entry on when to (and when not to) call GC.Collect 特定于 .NET Compact Framework,但规则通常适用于所有托管开发。


C
Carl Looper

在某些情况下,它比抱歉更安全。

这是一种情况。

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

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

安全总比后悔好。


这家伙有一个观点,这就是为什么所有这些“不要”的论点大多是无关紧要的。当您使用非托管库时,您所拥有的只是指向非托管对象的托管指针。由于 GC 认为这些指针非常小,因此不会急于收集它们。这就是为什么在实现 IDisposable 时对非托管对象进行 Dispose 至关重要的原因。
B
BankZ

简短的回答是:从不!


甚至在 this situation 中都没有?
D
Denis
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();
   //
}

J
Jay Tennant

另一个原因是您在 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;

N
Nacht

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


完成所有 GDI+ 对象后调用 Dispose() 是基本的 Winforms 编程。
s
svick

我对此仍然很不确定。我在应用服务器上工作了 7 年。我们更大的安装使用 24 GB 内存。它高度多线程,所有对 GC.Collect() 的调用都遇到了非常糟糕的性能问题。

许多第三方组件在他们认为现在这样做很聪明时使用了 GC.Collect()。因此,一堆简单的 Excel 报告每分钟多次阻塞所有线程的应用服务器。

我们必须重构所有 3rd 方组件以删除 GC.Collect() 调用,并且在这样做之后一切正常。

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

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

我想知道的一件事是 OOM 异常本身......如果我编写了 .Net 框架,但我无法分配内存块,我会使用 GC.Collect()、碎片整理内存 (??),再试一次,如果我仍然找不到可用的内存块,那么我会抛出 OOM 异常。

或者至少将此行为设置为可配置选项,因为 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。由于它具有应用程序范围的效果,因此只有应用程序应该这样做(如果有的话)。
If i would have written the .Net Framework, and i can't alloc a memory block, i would use GC.Collect(), - 我认为他们已经这样做了 - 我已经看到内部 GC 触发器之一是分配内存失败的迹象。
h
harry4516

调用 GC 的一个很好的理由是在内存很少的小型 ARM 计算机上,例如 Raspberry PI(使用单声道运行)。如果未分配的内存碎片占用了过多的系统 RAM,那么 Linux 操作系统可能会变得不稳定。我有一个应用程序,我必须每秒调用一次 GC(!)以摆脱内存溢出问题。

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


C
Chris S

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


f
fusi

如果您使用的 .net 版本低于 4.5,手动收集可能是不可避免的(尤其是在您处理许多“大对象”时)。

此链接描述了原因:

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


M
Max CHien

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

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

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