代码之家  ›  专栏  ›  技术社区  ›  Echostorm

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

  •  113
  • Echostorm  · 技术社区  · 16 年前

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

    15 回复  |  直到 6 年前
        1
  •  109
  •   Mark Ingram    16 年前

    最好的做法是不要强制垃圾收集。

    根据msdn:

    “强制垃圾是可能的 通过呼叫对方收款,但 大多数时候,这应该是 避免,因为它可能会 性能问题。”

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

    当你不再需要物品时,试着确保它们被清理干净。如果您有自定义对象,请使用“using语句”和IDisposable接口。

    在释放内存/垃圾收集等方面,此链接提供了一些很好的实用建议:

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

        2
  •  30
  •   StayOnTarget Charlie Flowers    6 年前

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

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

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

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

    可以 在仔细测试之后,您可以提出这样一种情况:在处理每个文件之后,您应该强制执行完整的垃圾收集。

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

    唯一一次我会考虑强迫 收藏就是当我知道很多 最近创建了个对象,共个 目前几乎没有物体 引用。

    我宁愿有一个垃圾收集API,当我可以给它关于这类事情的提示,而不必强迫gc我自己的时候。

    也见“ Rico Mariani's Performance Tidbits

        3
  •  28
  •   Maxam    16 年前

    这样看——当垃圾桶达到10%时,把厨房垃圾扔掉是更有效还是先把它装满再把它拿出来?

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

    当然,你可能会遇到这样一种情况:你不会很快在垃圾桶里添加任何东西——比如说,如果你打算去度假的话。那么,出去之前把垃圾扔掉是个好主意。

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

        4
  •  20
  •   Ian Ringrose    15 年前

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

        5
  •  15
  •   TomB    16 年前

    在编程中几乎没有绝对的通用指南。有一半的时候,当有人说“你做错了”时,他们只是在宣扬某种教条。在C语言中,它常常害怕诸如自我修改代码或线程之类的事情,在GC语言中,它强制GC,或者阻止GC运行。

    正如大多数准则和良好的经验法则(以及良好的设计实践)一样,很少有情况下按照既定的规范工作是有意义的。你必须非常确定你了解这个案例,你的案例真的需要废除惯例,并且你了解你可能造成的风险和副作用。但也有这样的情况。

    编程问题的种类繁多,需要灵活的方法。我见过这样的情况:用垃圾收集语言阻塞GC是有意义的,在某些地方触发GC是有意义的,而不是等待它自然发生。95%的时候,这两个都是没有正确处理问题的标志。但每20次就有1次,这可能是一个有效的理由。

        6
  •  13
  •   Mike de Klerk    9 年前

    我学会了不要试图胜过垃圾收集。说了这句话,我就坚持用 using 处理非托管资源(如文件I/O或数据库连接)时使用关键字。

        7
  •  9
  •   fabspro    11 年前

    我最近遇到的一个案例需要手动呼叫 GC.Collect() 是当工作在大的C++对象中,这些对象被包裹在微小的托管C++对象中,而这些对象又是从C++中访问的。

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

        8
  •  8
  •   Michael Stum    16 年前

    不确定这是否是一个最佳实践,但在循环中处理大量图像(即创建和处理大量图形/图像/位图对象)时,我定期让gc.collect。

    我想我在某个地方读到了gc只在程序(大部分)空闲时运行,而不是在一个密集循环的中间运行,所以它看起来像是一个手动gc可以理解的区域。

        9
  •  7
  •   Mitch Wheat    16 年前

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

    1. 您的代码中是否有在比需要的范围更大的范围内声明项的内容?
    2. 内存使用率真的太高了吗?
    3. 比较使用gc.collect()前后的性能,看看它是否真的有帮助。
        10
  •  5
  •   Morgan Cheng    16 年前

    假设您的程序没有内存泄漏,对象会累积,并且不能在第0代中进行gc-ed,因为: 1)它们被长期引用,因此进入gen1&gen2; 2)它们是大型对象(>80k),因此进入LOH(大型对象堆)。Loh不像Gen0、Gen1和Gen2那样进行压缩。

    检查“.net memory”的性能计数器,可以看出1)问题实际上不是问题。一般来说,每10个gen0 GC将触发1个gen1 GC,每10个gen1 GC将触发1个gen2 GC。理论上,如果GC0上没有压力(如果程序内存使用确实是有线的),则GC1和GC2永远无法进行GC ED。我从来没有发生过。

    对于问题2),您可以检查“.net memory”性能计数器以验证LOH是否膨胀。如果这真的是你的问题,也许你可以创建一个大型的对象池,正如这个博客所建议的那样。 http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .

        11
  •  4
  •   liggett78    16 年前

    大型对象是在LOH(大型对象堆)上分配的,而不是在Gen0上。如果你说他们不会被0代人收集垃圾,你是对的。我相信它们只有在完整的GC周期(第0、1和2代)发生时才被收集。

    也就是说,我相信在另一方面,当您处理大型对象时,GC将更积极地调整和收集内存,并且内存压力正在上升。

    很难说是不是要收集,在什么情况下。我以前在处理带有大量控件等的对话框窗口/窗体后执行gc.collect()(因为当窗体及其控件在第2代中结束时,由于创建了许多业务对象/加载了大量数据-显然没有大型对象),但实际上从长远来看,这样做并没有注意到任何正面或负面影响。

        12
  •  4
  •   Vaibhav    11 年前

    我想补充一下: 调用gc.collect()(+waitForPendingFinalizers())是本文的一部分。 正如其他人正确提到的,gc.collect()是非确定性集合,由gc本身(clr)决定。 即使添加对WaitForPendingFinalizer的调用,它也可能不具有确定性。 从这个msdn中获取代码 link 并以对象循环迭代为1或2运行代码。您将发现非确定性的含义(在对象的析构函数中设置一个断点)。 准确地说,当wait(等待)只有1(或2)个延迟对象时,不会调用析构函数。

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

    下面是一个有趣的例子:

    注释 :如果您已经从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()。这就是本质!:)

        13
  •  2
  •   Morgan Cheng    16 年前

    还有一件事,显式触发gc collect可能不会提高程序的性能。很有可能使情况变得更糟。

    .NET GC的设计和调优都很好,可以自适应,这意味着它可以根据程序内存使用的“习惯”调整gc0/1/2阈值。因此,它将在运行一段时间后适应您的程序。一旦您显式调用gc.collect,阈值将被重置!而且.NET必须花时间重新适应程序的“习惯”。

    我的建议总是信任.NET GC。如果出现内存问题,请检查“.net memory”性能计数器并诊断我自己的代码。

        14
  •  1
  •   user957965    13 年前

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

    建议:不确定时不要执行此操作或任何操作。当已知事实时重新评估,然后在性能测试之前/之后进行验证。

        15
  •  0
  •   Orion Edwards    16 年前

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

    imho,这类似于 “如果你能证明你的程序将来永远不会有任何错误,那就继续……”

    不管怎样,强制GC对于调试/测试都很有用。如果您觉得您需要在任何其他时间执行,那么您可能是错了,或者您的程序构建错误。不管怎样,解决方案都不会强制GC…