在C#中强制垃圾收集的最佳实践

时间:2008-10-24 13:49:44

标签: c# .net garbage-collection

根据我的经验,似乎大多数人会告诉你强制垃圾收集是不明智的,但在某些情况下,你正在使用大型对象,这些对象并不总是在0代收集但内存是问题,强制收集是否可以?这样做是否有最好的做法?

15 个答案:

答案 0 :(得分:110)

最佳做法是不强制进行垃圾回收。

根据MSDN:

  

“可以强迫垃圾   通过调用Collect收集,但是   大部分时间,这应该是   避免因为它可能会创造   性能问题。 “

但是,如果您可以可靠地测试您的代码以确认调用Collect()不会产生负面影响,那么请继续...

尝试确保在不再需要时清理对象。如果您有自定义对象,请查看“using statement”和IDisposable接口。

这个链接在释放内存/垃圾收集等方面有一些很好的实用建议:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

答案 1 :(得分:30)

最佳做法是在大多数情况下不强制进行垃圾收集。(我曾经处理的每个系统都强制进行垃圾收集,强调了问题,如果解决这个问题就会消除强迫的需要垃圾收集,大大加快了系统。)

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

然而,在某些批处理类型处理中,您确实了解GC。例如。考虑一个应用程序。

  • 在命令行上提供了文件名列表
  • 处理单个文件,然后将结果写入结果文件。
  • 在处理文件时,创建了许多相互关联的对象,在文件处理完成之前无法收集这些对象(例如,解析树)
  • 在处理过的文件之间没有多少状态

另一种情况是每隔几分钟醒来处理一些项目的服务,在睡着时不保持任何状态。然后在睡觉之前强制完整收集可能值得。

  

我唯一一次考虑强迫   一个集合就是我知道的很多   最近创建了对象   目前很少有物品   引用。

我宁愿拥有一个垃圾收集API,当我能给它提示这种类型的东西,而不必强迫GC自己。

另请参阅“Rico Mariani's Performance Tidbits

答案 2 :(得分:28)

以这种方式看待它 - 当垃圾桶达到10%时扔掉厨房垃圾还是让它在装满之前填满它是否更有效?

不要让它填满,你浪费时间往外走垃圾桶。这类似于GC线程运行时发生的情况 - 所有托管线程在运行时都被挂起。如果我没有弄错的话,GC线程可以在多个AppDomain之间共享,因此垃圾收集会影响所有这些。

当然,你可能会遇到这样一种情况:你不会很快在垃圾桶里添加任何东西 - 比方说,如果你要休假。然后,在外出之前把垃圾丢掉是个好主意。

这可能是迫使GC有所帮助的一次 - 如果您的程序空闲,则使用的内存不会被垃圾收集,因为没有分配。

答案 3 :(得分:20)

我认为Rico Mariani给出的示例很好:如果应用程序的状态发生重大变化,可能适合触发GC。例如,在文档编辑器中,可以在文档关闭时触发GC。

答案 4 :(得分:15)

编程中很少有一般指导方针是绝对的。有一段时间,当有人说'你做错了'时,他们只是在喷出一定量的教条。在C语言中,过去常担心自修改代码或线程,在GC语言中它强制GC或者阻止GC运行。

与大多数指南和良好的经验法则(以及良好的设计实践)一样,在极少数情况下,围绕既定规范工作是有意义的。你必须非常确定你理解这个案子,你的案件确实需要废除普通的做法,并且你了解你可能导致的风险和副作用。但是有这样的情况。

编程问题种类繁多,需要灵活的方法。我已经看到了在垃圾收集语言中阻止GC以及触发它而不是等待它自然发生的地方的情况。 95%的时间,其中任何一个都是没有正确处理问题的路标。但是在20年的一次,可能会有一个有效的案例。

答案 5 :(得分:13)

我已经学会了不要试图超越垃圾收集。话虽如此,我只是在处理非托管资源(如文件I / O或数据库连接)时坚持使用 using 关键字。

答案 6 :(得分:9)

不确定这是否是最佳做法,但在循环中处理大量图像时(即创建和处理大量图形/图像/位图对象),我经常让GC.Collect。

我想我在某处看到GC只在程序(大部分)空闲时运行,而不是在密集循环的中间运行,因此看起来像手动GC可能有意义的区域。

答案 7 :(得分:9)

我最近遇到过一个需要手动调用GC.Collect()的情况,就是在使用包含在很小的托管C ++对象中的大型C ++对象时,这些对象又是从C#访问的。

垃圾收集器从未被调用,因为使用的托管内存量可以忽略不计,但是使用的非托管内存量非常大。在对象上手动调用Dispose()将要求我跟踪自己何时不再需要对象,而调用GC.Collect()将清除任何不再引用的对象.....

答案 8 :(得分:7)

我认为你已经列出了最佳实践,除非真的需要,否则不要使用它。我强烈建议您更详细地查看您的代码,如果需要首先使用分析工具来回答这些问题。

  1. 您的代码中是否有一些声明项目范围大于所需范围的内容
  2. 内存使用率是否过高
  3. 比较使用GC.Collect()之前和之后的性能,看看它是否真的有用。

答案 9 :(得分:5)

假设您的程序没有内存泄漏,对象累积且无法在Gen 0中进行GC编辑,因为: 1)它们被长时间引用,因此进入Gen1&第二代; 2)它们是大对象(> 80K),因此进入LOH(大对象堆)。并且LOH不像Gen0,Gen1和amp;中那样进行压缩。第二代。

检查“.NET内存”的性能计数器可以看出1)问题确实不是问题。通常,每10个Gen0 GC将触发1个Gen1 GC,每10个Gen1 GC将触发1个Gen2 GC。理论上,GC1&如果GC0上没有压力(如果程序存储器使用情况确实有线),则GC2永远不能进行GC编辑。它永远不会发生在我身上。

对于问题2),您可以检查“.NET Memory”性能计数器以验证LOH是否变得臃肿。如果这确实是您的问题的一个问题,也许您可​​以创建一个大对象池,因为此博客建议http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx

答案 10 :(得分:4)

大对象在LOH(大对象堆)上分配,而不是在gen 0上。如果你说他们没有使用gen 0进行垃圾收集,那么你是对的。我相信只有在完整的GC循环(第0代,第1代和第2代)发生时才收集它们。

话虽这么说,我相信另一方面,当您处理大型物体并且内存压力上升时,GC会更积极地调整和收集内存。

很难说是否收集以及在何种情况下收集。在处理具有大量控件等的对话框窗口/表单之后,我曾经做GC.Collect()(因为当表格及其控件由于创建业务对象的多个实例/加载大量数据而在第2代结束时 - 很明显是大型物体,但实际上并没有注意到长期的任何积极或消极影响。

答案 11 :(得分:4)

我想补充一点: 调用GC.Collect()(+ WaitForPendingFinalizers())是故事的一部分。 正如其他人正确提到的那样,GC.COllect()是非确定性的集合,由GC本身(CLR)自行决定。 即使您添加对WaitForPendingFinalizers的调用,它也可能不是确定性的。 从这个msdn link获取代码,并以对象循环迭代为1或2运行代码。您将找到非确定性方法(在对象的析构函数中设置断点)。 确切地说,当Wait ...()中只有1个(或2个)延迟对象时,不会调用析构函数。[Citation reqd。]

如果您的代码处理非托管资源(例如:外部文件句柄),则必须实现析构函数(或终结器)。

这是一个有趣的例子:

注意:如果您已经在MSDN上尝试了上述示例,则以下代码将清除空气。

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

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

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

我建议,首先分析输出然后运行,然后阅读以下原因:

{只有在程序结束时才会隐式调用析构函数。 } 为了确定性地清理对象,必须实现IDisposable并显式调用Dispose()。这就是本质! :)

答案 12 :(得分:2)

另外一件事,明确触发GC Collect可能无法提高程序的性能。很可能会让情况变得更糟。

.NET GC经过精心设计和调整为自适应,这意味着它可以根据程序内存使用的“习惯”调整GC0 / 1/2阈值。因此,它会在运行一段时间后适应您的程序。一旦显式调用GC.Collect,阈值将被重置! .NET必须花时间再次适应你的程序的“习惯”。

我的建议总是信任.NET GC。任何内存问题浮出水面,检查“.NET内存”性能计数器并诊断我自己的代码。

答案 13 :(得分:1)

  
    

不确定这是否是最佳做法......

  

建议:不确定时不要实现这个或任何东西。在知道事实时重新评估,然后在性能测试之前/之后执行以进行验证。

答案 14 :(得分:0)

  

但是,如果您可以可靠地测试您的代码以确认调用Collect()不会产生负面影响,那么请继续...

恕我直言,这类似于说“如果你能证明你的程序将来不会有任何错误,那就继续......”

严肃地说,强制GC对于调试/测试非常有用。如果您觉得在其他任何时候都需要这样做,那么您错了,或者您的程序构建错误。无论哪种方式,解决方案都没有强制GC ......

相关问题