C# 何时可以呼叫GC.Collect?

C# 何时可以呼叫GC.Collect?,c#,.net,garbage-collection,C#,.net,Garbage Collection,一般的建议是,您不应该从代码中调用GC.Collect,但此规则有哪些例外 我只能想到一些非常具体的情况,在这些情况下强制垃圾收集可能是有意义的 脑海中浮现的一个例子是一个服务,它每隔一段时间醒来,执行一些任务,然后长时间睡眠。在这种情况下,强制执行collect可能是一个好主意,以防止即将空闲的进程占用超过需要的内存 有没有其他情况可以调用GC.Collect?看看Rico Mariani的这篇文章。当调用GC.Collect时,他给出了两条规则(规则1是:“不要”): 一种情况是,当您尝试对

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

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

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


有没有其他情况可以调用
GC.Collect

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


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

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

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

这样,您就不必担心调用GC.Collect了(您几乎不应该这样做)

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


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

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

更新日期:2018年7月2日

从.NET 4.5开始,有
GCLatencyMode.LowLatency
GCLatencyMode.sustainatedlowlatency
。在进入和离开这两种模式时,建议您使用
GC.Collect(2,GCCollectionMode.Forced)
强制执行完整GC

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

来源:微软工程师本·沃森:《编写高性能.NET代码》,2018年第二版

见:


斯科特·霍尔顿(Scott Holden)的规则是特定于的,但这些规则通常适用于所有的托管开发。

简单的回答是:决不

我仅在编写原油性能/剖面仪试验台时使用
GC.Collect
;i、 e.我有两个(或更多)代码块要测试-类似于:

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
使其非常接近临界点而受到冲击

一个典型的例子是一个简单的console exe(例如,一个足够在这里发布的
Main
方法排序),它显示了循环字符串连接和
StringBuilder
之间的区别

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


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

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

当您知道垃圾收集器不知道的应用程序的性质时,可以调用GC.Collect()。作为作者,很容易想到这一点。然而,事实是,GC相当于一个编写得非常好、经过测试的专家系统,而且很少有人知道它不知道的底层代码路径

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


但是,大多数情况下,GC都足够聪明,可以这样做。

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

您比垃圾收集器更了解内存使用情况时,很少有这种情况发生。在多用户应用程序或一次响应多个请求的服务中,这种情况不太可能发生

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

  • 在命令行上提供了一个文件名列表
  • 处理单个文件,然后将结果写入结果文件
  • 在处理文件时,会创建许多相互关联的对象,这些对象在文件处理完成之前无法收集(例如,解析树)
  • 不会在已处理的文件之间保持匹配状态
您可以(经过仔细的)测试,在处理完每个文件后,强制执行完整的垃圾收集

另一种情况是,服务每隔几分钟就会醒来处理一些项目,在睡眠时不保持任何状态
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
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();
   //
}
WeakReference w = CodeThatShouldNotMemoryLeak();
Assert.IsTrue(w.IsAlive);
GC.Collect();
GC.WaitForPendingFinalizers();
Assert.IsFalse(w.IsAlive);
        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
    }
        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();
    }
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
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));
}
numbers = null;
GC.Collect();
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;