【问题标题】:Best Practice for Forcing Garbage Collection in C#在 C# 中强制垃圾回收的最佳实践
【发布时间】:2010-09-19 00:22:27
【问题描述】:

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

【问题讨论】:

    标签: c# .net garbage-collection


    【解决方案1】:

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

    【讨论】:

    • 编译器?编译器与 GC 有什么关系? :)
    • 没什么,它只是编译和优化代码。这绝对与 CLR... 甚至 .NET 无关。
    • 占用大量内存的对象,比如非常大的图像,最终可能不会被垃圾收集,除非你明确地对它们进行垃圾收集。我认为这(大物体)或多或少是 OP 的问题。
    • 将其包装在 using 中将确保它在超出 using 范围后被安排进行 GC。除非计算机爆炸,否则内存很可能会被清理干净。
    【解决方案2】:

    不确定这是否是最佳实践,但在循环处理大量图像时(即创建和处理大量 Graphics/Image/Bitmap 对象),我经常让 GC.Collect。

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

    【讨论】:

    • 您确定需要这个吗? GC 收集它是否需要内存,即使您的代码不是空闲的。
    • 现在不确定它在 .net 3.5 SP1 中的情况,但以前(1.1,我相信我针对 2.0 进行了测试)它确实对内存使用产生了影响。当然,GC 总是会在需要时收集,但是当您只需要 20 个 RAM 时,您可能最终还是会浪费 100 Megs 的 RAM。不过需要更多的测试
    • 当第 0 代达到某个阈值(例如 1MB)时,在内存分配上触发 GC,而不是在“某些东西空闲”时触发。否则,您可能会通过简单地分配并立即丢弃对象而在循环中结束 OutOfMemoryException。
    • 如果没有其他进程需要,100megs 的 RAM 不会被浪费。它给你一个很好的性能提升:-P
    【解决方案3】:

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

    1. 您的代码中是否有一些东西在比需要更大的范围内声明项目
    2. 内存使用率真的太高了
    3. 比较使用 GC.Collect() 前后的性能,看看它是否真的有帮助。

    【讨论】:

      【解决方案4】:

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

      根据 MSDN:

      "可以强制垃圾 通过调用 Collect 来收集,但是 大多数时候,这应该是 避免,因为它可能会产生 性能问题。 "

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

      请确保在您不再需要对象时将其清理干净。如果您有自定义对象,请查看使用“using 语句”和 IDisposable 接口。

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

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

      【讨论】:

      • 另外,你可以设置不同的LatencyMode的msdn.microsoft.com/en-us/library/bb384202.aspx
      • 如果您的对象指向非托管内存,您可以通过 GC.AddMemoryPressure Api (msdn.microsoft.com/en-us/library/…) 让垃圾收集器知道。这为垃圾收集器提供了有关您系统的更多信息,而不会干扰收集算法。
      • +1: * 看看使用“using 语句”和 IDisposable 接口。* 我什至不会考虑强制,除非作为最后的手段 - 好建议(读作“免责声明”)。但是,我在单元测试中强制收集以模拟在后端操作中丢失活动引用 - 最终抛出 TargetOfInvocationNullException
      【解决方案5】:

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

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

      很难说是否收集以及在什么情况下收集。我曾经在处理带有大量控件等的对话框窗口/表单后执行 GC.Collect() (因为由于创建了许多业务对象实例/加载了大量数据,表单及其控件最终进入第 2 代 - 不显然是大物体),但实际上并没有注意到长期这样做的任何积极或消极影响。

      【讨论】:

        【解决方案6】:

        假设您的程序没有内存泄漏,对象会累积并且无法在 Gen 0 中进行 GC,因为: 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

        【讨论】:

          【解决方案7】:

          还有一点,显式触发 GC Collect 可能不会提高程序的性能。很可能使情况变得更糟。

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

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

          【讨论】:

          • 我认为最好将此答案与之前的答案合并。
          【解决方案8】:

          这样看——厨房垃圾是在垃圾桶有10%的时候扔掉,还是先装满再拿出来?

          不让它填满,就是在浪费时间往返外面的垃圾箱。这类似于 GC 线程运行时发生的情况——所有托管线程在运行时都被挂起。而且如果我没记错的话,GC线程可以在多个AppDomain之间共享,所以垃圾回收会影响所有的。

          当然,您可能会遇到短期内不会向垃圾桶添加任何东西的情况 - 例如,如果您要休假。那么,出门前把垃圾扔掉是个好主意。

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

          【讨论】:

          • 如果你有一个婴儿,如果你离开它超过一分钟就会死,而你只有一分钟来处理垃圾,那么你每次都想做一点一下子。不幸的是,GC::Collect() 方法并不是越频繁调用它就越快。因此,对于实时引擎,如果您不能只使用处置机制并让 GC 汇集您的数据,那么您不应该使用托管系统——根据我的回答(可能低于这个,哈哈)。跨度>
          • 在我的例子中,我正在运行一个 A*(最短路径)算法来反复调整它的性能......在生产中,它只会运行一次(在每个“地图”上)。因此,我希望在每次迭代之前完成 GC,在我的“性能测量块”之外,因为我觉得更接近于模拟生产中的情况,其中 GC 可以/应该在导航每个“地图”之后强制执行。
          • 在测量块之前调用 GC 实际上并不能模拟生产中的情况,因为在生产中 GC 将在不可预测的时间内完成。为了缓解这种情况,您应该进行长时间的测量,包括多次 GC 执行,并将 GC 期间的峰值纳入分析和统计中。
          【解决方案9】:

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

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

          说真的,强制 GC 对于调试/测试目的很有用。如果你觉得你需要在其他任何时候这样做,那么要么你错了,要么你的程序构建错误。无论哪种方式,解决方案都不会强制 GC...

          【讨论】:

          • "那么要么你弄错了,要么你的程序构建错误。无论哪种方式,解决方案都不会强制 GC..." 绝对值几乎总是不正确的。有一些特殊情况是有道理的。
          【解决方案10】:

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

          【讨论】:

          • 或者就在打开一个大的连续对象之前,该对象显示了失败的历史并且没有提供有效的解决方案来增加其粒度。
          【解决方案11】:

          编程中很少有绝对的通用准则。有一半的时候,当有人说“你做错了”时,他们只是在宣扬一定数量的教条。在 C 语言中,它曾经担心诸如自修改代码或线程之类的事情,在 GC 语言中,它正在强制 GC 或阻止 GC 运行。

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

          编程问题多种多样,需要灵活的方法。我已经看到了在垃圾收集语言中阻止 GC 有意义的情况以及触发它而不是等待它自然发生的地方是有意义的。 95% 的情况下,这两种情况中的任何一种都是没有正确解决问题的标志。但 20 次中有 1 次,可能有一个有效的理由。

          【讨论】:

            【解决方案12】:

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

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

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

            • 在命令行中给出了文件名列表
            • 处理单个文件,然后将结果写入结果文件。
            • 在处理文件时,会创建许多在文件处理完成之前无法收集的相互关联的对象(例如解析树)
            • 在已处理的文件之间不保留太多状态

            也许能够进行一个案例(经过仔细的)测试,在处理完每个文件后,您应该强制进行一次完整的垃圾回收。

            另一种情况是服务每隔几分钟就会唤醒以处理某些项目,并且在睡眠时不保持任何状态。然后在临睡前强制收集完整的数据可能是值得的。

            唯一一次我会考虑强迫 收藏就是当我知道很多的时候 的对象最近已创建 目前很少有物体 参考。

            我宁愿有一个垃圾回收 API,因为我可以给它关于这类事情的提示,而不必强迫我自己进行 GC。

            另见“Rico Mariani's Performance Tidbits

            【讨论】:

            • 类比:Playschool(系统)保留蜡笔(资源)。根据孩子(任务)的数量和颜色的稀缺性,老师(.Net)决定如何在孩子之间分配和分享。当需要一种稀有颜色时,教师可以从池中分配或查找未使用的颜色。教师有权定期收集未使用的蜡笔(垃圾收集)以保持物品整洁(优化资源使用)。一般来说,家长(程序员)无法预先确定最佳的课堂蜡笔整理策略。一个孩子预定的午睡不太可能是干扰其他孩子着色的好时机。
            • @AlanK,我喜欢这样,因为当孩子们回家一天的时候,这是一个很好的时间让老师帮手好好打扫卫生,孩子们不会碍事。 (我最近工作过的系统在强制 GC 的时候刚刚重新启动了服务进程。)
            【解决方案13】:

            不确定这是否是最佳实践...

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

            【讨论】:

              【解决方案14】:

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

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

              【讨论】:

              • 解决这个问题的更好方法是在构造函数中调用GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource),然后在终结器中调用GC.RemoveMemoryPressure(addedSize)。这样垃圾收集器将自动运行,同时考虑到可以收集的非托管结构的大小。 stackoverflow.com/questions/1149181/…
              • 解决问题的更好方法是实际调用 Dispose(),无论如何你都应该这样做。
              • 最好的方法是使用 Using 结构。 Try/Finally .Dispose 很麻烦
              【解决方案15】:

              我想补充一点: 调用 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() 进行显式调用。这就是本质! :)

              【讨论】:

                【解决方案16】:

                我不推荐手动垃圾回收。我向您保证,您没有正确处理大型物体。使用 USING 语句。每当您实例化一个对象时,请务必在使用完它时将其丢弃。此示例代码使用 USING 语句创建连接。然后它实例化一个运输标签对象,使用它,并正确地处理它。

                         Using con As SqlConnection = New SqlConnection(DB_CONNECTION_STRING)
                            con.Open()
                
                            Using command As SqlCommand = New SqlCommand(sqlStr, con)
                                Using reader As SqlDataReader = command.ExecuteReader()
                
                                    While reader.Read()
                                        code_here()
                                    End While
                                End Using
                            End Using
                        End Using
                        Dim f1 As frmShippingLabel
                        f1 = New frmShippingLabel
                        f1.PrintLabel()
                        f1.Dispose()
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-04-07
                  • 2013-08-24
                  • 2010-11-09
                  • 2010-11-01
                  • 2021-07-06
                  相关资源
                  最近更新 更多