代码之家  ›  专栏  ›  技术社区  ›  Brian Rasmussen

什么时候可以打电话给GC。收集?

  •  206
  • Brian Rasmussen  · 技术社区  · 17 年前

    一般的建议是,你不应该打电话 GC.Collect 从你的代码中,但这个规则的例外是什么?

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

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

    还有其他可以打电话的情况吗 GC。收集 ?

    23 回复  |  直到 4 年前
        1
  •  164
  •   Ben Hall    8 年前

    如果你有充分的理由相信,一组重要的对象,特别是那些你怀疑属于第1代和第2代的对象,现在有资格进行垃圾收集,从性能影响的角度来看,现在是收集垃圾的合适时机。

    一个很好的例子是,如果你刚刚关闭了一个大型窗体。你知道现在所有的UI控件都可以被垃圾回收,并且窗体关闭时的短暂停顿可能不会被用户注意到。

    2018年7月更新

    截至。NET 4.5-有 GCLatencyMode.LowLatency GCLatencyMode.SustainedLowLatency 。当进入和离开这两种模式时,建议您使用以下命令强制执行完整GC GC.Collect(2, GCCollectionMode.Forced) .

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

    来源:微软工程师本·沃森: 写高性能。NET代码 ,2018年第2版。

    请参阅:

        2
  •  53
  •   Vlad Schnakovszki Rohit kumar    9 年前

    我用 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() 以尽可能相似的状态运行,即。 测试B() 不会仅仅因为 TestA 使其非常接近临界点。

    一个经典的例子是一个简单的控制台exe(A Main 例如,方法sort足够在这里发布),它显示了循环字符串连接和 StringBuilder .

    如果我需要一些精确的东西,那么这将是两个完全独立的测试——但如果我们只是想在测试期间最小化(或规范化)GC以大致了解行为,这通常就足够了。

    生产代码期间?我还没有使用它;p

        3
  •  29
  •   Ian Ringrose    16 年前

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

    有一个 少数病例 什么时候 比垃圾收集器更了解内存使用情况。在多用户应用程序或一次响应多个请求的服务中,这不太可能是真的。

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

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

    五月 能够(在仔细测试后)证明,在处理每个文件后,应该强制进行完全垃圾收集。

    另一种情况是,每隔几分钟就会唤醒一次服务来处理一些项目 在睡眠时不保持任何状态 。然后在睡觉前强制收集全部物品 五月 值得。

    我唯一一次会考虑强迫 收藏是我知道很多的时候 最近创建了一个对象 目前很少有物体 引用。

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

    另见“ Rico Mariani's Performance Tidbits "


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

        4
  •  20
  •   Brian    17 年前

    一种情况是,当您试图使用以下代码进行单元测试时 WeakReference .

        5
  •  13
  •   Paul Ruane    15 年前

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

    拥有这种机制可以更容易地分析内存使用情况,因为报告不会被活动处理的噪音所掩盖。

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

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

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

        6
  •  12
  •   Joel Coehoorn    4 年前

    你可以打电话 GC.Collect() 当你知道应用程序的性质时,垃圾收集器就不知道了。

    作为作者,人们往往倾向于认为这是可能的或正常的。然而,事实是GC相当于一个编写良好、经过测试的专家系统,你很少会知道它不知道的低级代码路径。

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

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

        7
  •  8
  •   Ethos    12 年前

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

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

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

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

        8
  •  8
  •   Daniel B    8 年前

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

        9
  •  7
  •   M4N    17 年前

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

    When to call GC.Collect()

        10
  •  7
  •   garthhh    9 年前

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

    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 在GetSomeNumbers_Enumerable_Range方法中,唯一的解决方法是通过以下方式释放内存:

    numbers = null;
    GC.Collect();
    
        11
  •  4
  •   casperOne    17 年前

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

        12
  •  4
  •   Brandon Barkley    7 年前

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

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

    这样,你就不用担心给GC打电话了。收集(你应该 很少 如果有的话,必须这样做)。

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

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

        13
  •  3
  •   Carl Looper    14 年前

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

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

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

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

        14
  •  3
  •   ChaseMedallion    13 年前

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

    这里有一种情况。

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

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

    安全总比后悔好。

        15
  •  3
  •   mtekeli    12 年前

    如果你正在创造很多新的 System.Drawing.Bitmap 对象,垃圾回收器不会清除它们。最终GDI+会认为内存不足,并抛出“参数无效”异常。使命感 GC.Collect() 偶尔(不要太频繁!)似乎可以解决这个问题。

        16
  •  2
  •   BankZ    17 年前

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

        17
  •  2
  •   Denis    14 年前
    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();
       //
    }
    
        18
  •  2
  •   Nacht    6 年前

    另一个原因是当您在USB COM端口上打开SerialPort,然后拔下USB设备时。由于SerialPort已打开,资源在系统注册表中保存了对以前连接的端口的引用。然后,系统的注册表将 contain stale data ,因此可用端口列表将是错误的。因此,必须关闭港口。

    正在调用串行端口。端口上的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;
    
        19
  •  1
  •   svick Raja Nadar    12 年前

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

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

    为了删除GC,我们不得不重构所有第三方组件。Collect()调用,执行此操作后一切正常。

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

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

    我想知道的一件事是OOM异常本身。.. 如果我写的话。Net Framework,我不能分配内存块,我会使用GC。Collect(),对内存进行碎片整理(??),再试一次,如果我仍然找不到空闲内存块,那么我会抛出OOM异常。

    或者至少将此行为设置为可配置选项,因为GC存在性能问题的缺点。收集。

    现在,我的应用程序中有很多这样的代码来“解决”这个问题:

    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()行为是一个真正特定于应用程序的行为,因为我们正在运行一个ORM缓存服务,如果RAM超过一些预定义值,该服务需要一些时间来释放所有缓存的对象。因此,它第一次等待几秒钟,每次发生OOM都会增加等待时间。)

        20
  •  1
  •   Jay Tennant    6 年前

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

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

        21
  •  0
  •   Chris S    17 年前

    这与问题无关,但与XSLT转换有关。NET(XSLCompiledTranform),那么您可能别无选择。另一个候选者是MSHTML对照。

        22
  •  0
  •   fusi    9 年前

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

    此链接描述了原因:

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

        23
  •  0
  •   harry4516    9 年前

    因为有小对象堆(SOH)和大对象堆(LOH)

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

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

        24
  •  0
  •   Max CHien    8 年前