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

Ech*_*orm 116 .net c# garbage-collection

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

Mar*_*ram 110

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

根据MSDN:

"可以通过调用Collect来强制进行垃圾收集,但大多数情况下,这应该避免,因为它可能会产生性能问题."

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

只是在不再需要时确保清理对象.如果您有自定义对象,请查看"using statement"和IDisposable接口.

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

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

  • 如果您的对象指向非托管内存,您可以通过GC.AddMemoryPressure Api(http://msdn.microsoft.com/en-us/library/system.gc.addmemorypressure.aspx)了解垃圾收集器.这为垃圾收集器提供了有关系统的更多信息,而不会干扰收集算法. (4认同)
  • 此外,您可以设置不同的LatencyMode的http://msdn.microsoft.com/en-us/library/bb384202.aspx (3认同)

Ian*_*ose 31

最佳做法是在大多数情况下不强制进行垃圾收集. (我所研究过的每个系统都强制进行垃圾收集,强调了如果解决问题就会消除强制垃圾收集的需要,并大大加快系统速度.)

在某些情况下,您可以了解有关内存使用情况的更多信息.在多用户应用程序或一次响应多个请求的服务中,这不可能成立.

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

  • 在命令行上给出了文件名列表
  • 处理单个文件,然后将结果写入结果文件.
  • 处理文件时,会创建大量互连对象,在文件处理完成之前无法收集这些对象(例如,解析树)
  • 在它处理的文件之间没有多少状态.

可能能够(经过仔细测试)测试后应该在处理完每个文件后强制执行完整的垃圾回收.

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

我唯一一次考虑强制收集的是当我知道最近创建了很多对象时,当前引用的对象非常少.

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

另见" Rico Mariani的表演花絮 "

  • 打个比方:Playschool(系统)保持蜡笔(资源).根据孩子(任务)的数量和颜色的稀缺性,教师(.Net)决定如何在孩子之间分配和分享.当请求稀有颜色时,教师可以从池中分配或查找未使用的颜色.教师可以自行定期收集未使用的蜡笔(垃圾收集)以保持整洁(优化资源使用).通常,父母(程序员)无法预先确定最佳课堂蜡笔整理政策.一个孩子的预定午睡不太可能是干扰其他孩子着色的好时机. (2认同)

Max*_*xam 30

看看这样的方式 - 是更有效的抛出了厨房垃圾时,垃圾可以在10%或服用之前让它填补?

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

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

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

  • 如果你有一个婴儿,如果你离开它超过一分钟会死,而你只有一分钟处理垃圾,那么你想要每次做一点而不是一次.不幸的是,GC :: Collect()方法不是更快,你调用它的频率越高.因此,对于实时引擎,如果您不能只使用dispose机制并让GC汇集您的数据,那么您不应该使用托管系统 - 根据我的回答(可能在这个下面,大声笑). (5认同)

den*_*ips 21

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

  • 或者就在打开一个显示失败历史的大型连续对象之前,并且没有提高其粒度的有效分辨率. (2认同)

小智 16

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

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

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


Kon*_*Kon 12

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

  • 编译器?编译器与GC有什么关系?:) (27认同)

Mic*_*tum 9

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

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

  • 如果没有其他进程需要,则不会浪费100兆的RAM.它给你带来了不错的性能提升:-P (5认同)
  • 当第0代达到某个阈值(例如1MB)时,GC会在内存分配时触发,而不是在"某些东西空闲"时触发.否则,您可以通过简单地分配并立即丢弃对象来在循环中结束OutOfMemoryException. (2认同)

小智 9

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

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

  • 解决这个问题的更好方法是在构造函数中调用`GC.AddMemoryPressure(ApproximateSizeOfUnmanagedResource)`,然后在终结器中调用`GC.RemoveMemoryPressure(addedSize)`.这样,垃圾收集器将自动运行,同时考虑到可以收集的非托管结构的大小.http://stackoverflow.com/questions/1149181/what-is-the-point-of-using-gc-addmemorypressure-with-an-unmanaged-resource (6认同)
  • 最好的方法是使用Using结构.尝试/最后.目的是一个麻烦 (2认同)

Mit*_*ers 7

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

  1. 您的代码中是否有一些声明项目范围超出需要的内容
  2. 内存使用量是否真的太高了
  3. 比较使用GC.Collect()之前和之后的性能,看它是否真的有帮助.


Mor*_*eng 5

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

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

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


Vai*_*hav 5

我想补充一点:调用 GC.Collect() (+ WaitForPendingFinalizers()) 是故事的一部分。正如其他人正确提到的,GC.COllect() 是非确定性收集,由 GC 本身 (CLR) 自行决定。即使您添加对 WaitForPendingFinalizers 的调用,它也可能不是确定性的。从此 msdn链接获取代码,并使用对象循环迭代为 1 或 2 运行代码。您将发现非确定性意味着什么(在对象的析构函数中设置断点)。准确地说,当只有 1 个(或 2 个)延迟对象时,不会调用析构函数 Wait..()。[引用要求]

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

这是一个有趣的例子:

注意:如果您已经尝试过 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 = "";
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

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