正确使用GC.Collect的(); GC.WaitForPendingFinalizers()

2019-06-27 19:21发布

我已经开始审查项目的一些代码,发现是这样的:

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

这些行通常显示在被设想为破坏的增加效率的理由下的对象的方法。 我做了这样的言论:

  1. 显式调用垃圾回收中的每个对象的破坏,因为这样做并没有考虑到,如果它是CLR性能绝对有必要降低性能。
  2. 调用的顺序这些指令使只有正在最后定稿,其他对象每一个对象被销毁。 因此,可以摧毁一个独立的对象必须等待另一个对象的破坏没有一个真正的必要性。
  3. 它可以产生死锁(见: 这个问题 )

1,2和3是真的吗? 您能否给一些参考支持你的答案?

虽然我几乎可以肯定我的话,我需要清楚在我的论点,以为什么这是一个问题的解释给我的团队。 这就是我所要求的确认和参考的原因。

Answer 1:

简短的回答是:把它拿出来。 该代码几乎不会提高性能,或长期内存使用。

你的所有的点是真实的。 (它可以产生死锁,这并不意味着它永远 。)调用GC.Collect()将收集所有的GC几代人的记忆。 这做了两两件事。

  • 每一次收集所有代-而不是什么GC将默认,这是只收集了一代已满时,做的。 典型的使用将会看到GEN0收集(大约)的十倍往往比的Gen1,这反过来又收集(大约)十倍尽可能经常的Gen2。 每一次该代码会收集所有代。 GEN0集通常是分100毫秒; 第二代可以更长。
  • 它促进下一代非收藏的对象。 也就是说,每次强制收集,你还有一些对象的引用,该对象将被提升到后代。 通常,这会相对很少发生,但这样的代码下面会更经常迫使这样的:

     void SomeMethod() { object o1 = new Object(); object o2 = new Object(); o1.ToString(); GC.Collect(); // this forces o2 into Gen1, because it's still referenced o2.ToString(); } 

如果没有GC.Collect()这两个项目将在下次有机会收集。 收集作为被软件写, o2这意味着一个自动化GEN0集合将不会释放存储器-将在第一代结束。

另外值得一提的一个更大的恐怖:在调试模式下,GC的功能各不相同,并不会回收任何变量仍处于范围(即使它不是在当前的方法以后使用)。 所以在调试模式下,上面的代码甚至不会收集o1时调用GC.Collect ,所以这两个o1o2将得到提升。 调试代码时,这可能会导致一些非常古怪和意想不到的内存使用情况。 (文章如这个亮点这种行为。)

编辑:具有刚刚测试过这种行为,一些真正具有讽刺意味的:如果你有一个方法是这样的:

void CleanUp(Thing someObject)
{
    someObject.TidyUp();
    someObject = null;
    GC.Collect();
    GC.WaitForPendingFinalizers(); 
}

...那么它会明确规定不释放someObject的内存,即使在释放模式:它会促使它进入下一个GC的产生。



Answer 2:

有一点可以做,这是非常容易理解的:有GC运行自动清理每次运行许多对象(比如,10000)。 调用它后每毁灭清理每次运行约一个对象。

由于GC具有较高的开销(需要停止和启动线程,需要扫描所有对象活着)配料电话是非常可取的。

此外,有什么能来的每一个对象后清理出来? 这怎么能比配料更有效?



Answer 3:

你点3号在技术上是正确的,但如果一个finaliser期间有人仅锁定可能发生。

即使没有这种调用,一个finaliser内锁定比你这里有什么更糟糕。

调用时,有次屈指可数GC.Collect()确实有助于提高性能。

到目前为止,我已经在我的职业生涯做了2,也许3倍。 (或者约5至6次,如果你包括那些我做到了,测量结果,然后再拿出来-这是你应该做的之后经常测量)。

在你通过数百或数千兆内存在的短时间内搅动,然后切换到集约利用更少的内存的很长一段时间的情况下,它可以是一个巨大的甚至是至关重要的改善明确收集。 是这里发生了什么?

在别的地方,他们充其量要使它更慢,使用更多的内存。



Answer 4:

看到我其他的答案:

为了GC.Collect的或没有?

当你调用GC.Collect(两件事情可能发生),你自己:你最终花费更多的时间做集合(因为正常的背景藏品将此外还发生在你手动GC.Collect的()),你会挂到内存较长 (因为你迫使一些东西放进高阶一代并不需要去那里)。 换句话说,使用GC.Collect的()自己几乎总是一个坏主意。

关于你曾经想要调用GC.Collect()唯一一次自己是当你有关于您的具体信息,是很难垃圾收集器就知道了。 典型的例子是具有鲜明忙和轻负载周期长期运行的程序。 您可能要强制征收近一个时期轻负载的结束,提前忙周期的,以确保资源尽可能免费为忙周期。 但即使在这里,你可能会发现你通过重新思考你的应用程序是如何构建的做的更好(即,将计划任务更好地工作?)。



Answer 5:

我用这只是一次:清理水晶报表文件的服务器端缓存。 见我回应的Crystal Reports例外:最大报告处理作业限制由系统管理员配置已经达到

该WaitForPendingFinalizers对我来说特别有用,因为有时对象没有被清理正确。 考虑到在网页中报告的相对缓慢性能 - 任何轻微的GC延迟是可以忽略不计,并且在内存管理的改善了总体幸福服务器我。



Answer 6:

我们遇到了类似的问题,但是@Grzenio我们正在与更大的2维数组的工作,在1000×1000的顺序3000x3000,这是一个Web服务。

添加更多的内存并不总是正确的答案,你必须了解你的代码和使用情况。 如果没有GC收集我们需要的内存16-32gb(根据客户的大小)。 没有它,我们需要的内存32-​​64gb,即使这样也不能保证系统不会受到影响。 在.NET垃圾收集器是不完美的。

我们的web服务具有内存中缓存在5-50百万串的次序(〜每键/值对80-140字符取决于配置)中,除了与每一个客户请求,我们将构建的双重2个矩阵之一,一个布尔然后将其传递到另一个服务做的工作。 对于1000×1000“基质”(2维阵列),这是〜25MB, 每个请求 。 布尔说我们需要哪些元素(基于我们的高速缓存)。 每个高速缓存条目代表一个“细胞”中的“矩阵”。

高速缓存性能时,该服务器具有> 80%的内存利用率急剧下降,由于寻呼。

我们发现的是,除非我们明确GC的.NET垃圾收集器永远不会“清理”的短暂变数,直到我们在90-95%的范围内由点高速缓存的性能有显着下降。

由于下游过程往往花费很长的时间(3-900秒)GC收集的性能损失是可忽略的(每收集3-10秒)。 我们发起这个收集后,我们就已经返回到客户端的响应。

最终,我们取得了GC参数配置,还与.NET 4.6还有更多的选择。 下面是我们使用了.NET 4.5的代码。

if (sinceLastGC.Minutes > Service.g_GCMinutes)
{
     Service.g_LastGCTime = DateTime.Now;
     var sw = Stopwatch.StartNew();
     long memBefore = System.GC.GetTotalMemory(false);
     context.Response.Flush();
     context.ApplicationInstance.CompleteRequest();
     System.GC.Collect( Service.g_GCGeneration, Service.g_GCForced ? System.GCCollectionMode.Forced : System.GCCollectionMode.Optimized);
     System.GC.WaitForPendingFinalizers();
     long memAfter = System.GC.GetTotalMemory(true);
     var elapsed = sw.ElapsedMilliseconds;
     Log.Info(string.Format("GC starts with {0} bytes, ends with {1} bytes, GC time {2} (ms)", memBefore, memAfter, elapsed));
}

重写与.NET 4.6使用后大家平分垃圾colleciton到2步 - 一个简单的收集和压实收集。

    public static RunGC(GCParameters param = null)
    {
        lock (GCLock)
        {
            var theParams = param ?? GCParams;
            var sw = Stopwatch.StartNew();
            var timestamp = DateTime.Now;
            long memBefore = GC.GetTotalMemory(false);
            GC.Collect(theParams.Generation, theParams.Mode, theParams.Blocking, theParams.Compacting);
            GC.WaitForPendingFinalizers();
            //GC.Collect(); // may need to collect dead objects created by the finalizers
            var elapsed = sw.ElapsedMilliseconds;
            long memAfter = GC.GetTotalMemory(true);
            Log.Info($"GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");

        }
    }

    // https://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode.aspx
    public static RunCompactingGC()
    {
        lock (CompactingGCLock)
        {
            var sw = Stopwatch.StartNew();
            var timestamp = DateTime.Now;
            long memBefore = GC.GetTotalMemory(false);

            GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
            GC.Collect();
            var elapsed = sw.ElapsedMilliseconds;
            long memAfter = GC.GetTotalMemory(true);
            Log.Info($"Compacting GC starts with {memBefore} bytes, ends with {memAfter} bytes, GC time {elapsed} (ms)");
        }
    }

希望这可以帮助别人,因为我们花了很多时间研究这个。



Answer 7:

这里的答案大致同意 - 有些时候你应该做你自己的垃圾收集极少数情况下,作为原生Python实现是比较有效的,可预测的。 我可以看到两种情况时,它才有意义采取事态在你自己的手中:

  1. 你有一个接近实时的应用程序,并且默认的垃圾收集是在不方便的时候被触发。 你也应该质疑为什么你在Python的那一刻编写实时敏感的代码,也; 和
  2. 您正在调试的内存泄漏 - 垃圾回收立即获取内存使用情况,并寻找有趣的东西之前,减少杂乱; 外最基本的功能,你可能会得到更好的使用现有的库了,虽然。


文章来源: Is correct to use GC.Collect(); GC.WaitForPendingFinalizers();?