【问题标题】:Memory leaks in .NET [closed].NET 中的内存泄漏 [关闭]
【发布时间】:2010-09-06 10:19:36
【问题描述】:

在 .NET 中,我们可以通过哪些方式获得内存泄漏?

我知道两个:

  1. 未正确注销Event Handlers/Delegates
  2. 不在 Windows 窗体中释放动态子控件:

例子:

// Causes Leaks  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  

// Correct Code  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  
label.Dispose();

更新:这个想法是列出不太明显的常见陷阱(例如上面的)。通常的想法是,由于垃圾收集器,内存泄漏不是一个大问题。不像以前在 C++ 中那样。


大家讨论得很好,但让我澄清一下……根据定义,如果 .NET 中没有对对象的引用,那么它会在某个时候被垃圾收集。所以这不是导致内存泄漏的方法。

在托管环境中,如果您意外引用了您不知道的任何对象(因此我的问题中有两个示例),我会认为这是内存泄漏。

那么,发生这种内存泄漏的各种可能方式是什么?

【问题讨论】:

  • 正如基思所说,您的样本不会导致内存泄漏。

标签: .net optimization memory-leaks


【解决方案1】:

您是在谈论意外的内存使用还是实际泄漏?您列出的两种情况并不完全是泄漏;它们是物体停留时间超过预期的情况。

换句话说,它们是指称它们为内存泄漏的人不知道或忘记的引用。

编辑:或者它们是垃圾收集器或非托管代码中的实际错误。

编辑 2:另一种思考方式是始终确保对对象的外部引用得到适当的释放。外部意味着您无法控制的代码。发生这种情况的任何情况都是您可以“泄漏”内存的情况。

【讨论】:

    【解决方案2】:

    阻塞终结器线程。在终结器线程被解除阻塞之前,不会对其他对象进行垃圾收集。因此,使用的内存量将不断增长。

    延伸阅读:http://dotnetdebug.net/2005/06/22/blocked-finalizer-thread/

    【讨论】:

    • 这是什么意思?
    • 终结器是单线程的。 'Finalizing' 是当一个可以被释放的对象最终被释放时发生的事情。如果一个特定的项目不能被释放,那么什么都不会被释放,你最终会耗尽内存。
    • 那么上下文中的“阻塞”是什么意思——覆盖和代码包装终结器函数,或者完全阻止它运行?
    • 是的,因为这被标记为答案,请用完整的句子等详细说明。
    • 我相信他只是意味着在一个永远不会完成的终结器中使用代码 - 无限循环或死锁或类似
    【解决方案3】:

    Tess Fernandez 有很多关于查找和调试内存泄漏的博文。 Lab 6Lab 7

    【讨论】:

      【解决方案4】:

      这并不会真正导致泄漏,它只是为 GC 增加了工作量:

      // slows GC
      Label label = new Label();  
      this.Controls.Add(label);  
      this.Controls.Remove(label);  
      
      // better  
      Label label = new Label();  
      this.Controls.Add(label);  
      this.Controls.Remove(label);  
      label.Dispose();
      
      // best
      using( Label label = new Label() )
      { 
          this.Controls.Add(label);  
          this.Controls.Remove(label);  
      }
      

      在像 .Net 这样的托管环境中,像这样放置一次性组件从来都不是什么大问题 - 这是托管意义的重要组成部分。

      你肯定会减慢你的应用程序的速度。但你不会因为其他任何事情而一团糟。

      【讨论】:

      • @Keith 不幸的是,最后一个不起作用(因为添加和删除通常不会在同一个地方 - 我只是为了演示问题)。此外,它不仅会降低 GC 速度,根据表单的复杂性,您还可以很容易地使应用程序崩溃。
      • 把控件放在一边是很危险的。小型一次性应用程序是可以的,但您应该始终在实际应用程序中处理它们,否则您会后悔的。相信我,我去过那里。
      • 我遇到了这个,这是一件危险的事情 - 留下一次性用品!要对此进行测试,请在循环中创建并删除 System.Drawing.Bitmap - 您很快就会得到 OutOfMemoryException,而 GC 也无济于事。
      • @modosansreves - 是的,这肯定会破坏你的应用程序。但是,因为您获得了托管的OutOfMemoryException,所以您只会使该应用程序崩溃。非托管内存泄漏将导致整个机器蓝屏、挂起或以其他方式崩溃。
      【解决方案5】:

      没有办法提供一份完整的清单……这很像问“你怎么会湿身?”

      也就是说,请确保对实现 IDisposable 的所有对象都调用 Dispose(),并确保对消耗任何类型的非托管资源的任何类型都实现 IDisposable。

      时不时地,在您的代码库上运行类似 FxCop 的东西来帮助您执行该规则 - 您会惊讶于某些一次性对象在应用程序框架中埋藏的深度。

      【讨论】:

      • 您将如何设置 FxCop 以执行该规则?
      【解决方案6】:

      我有 4 个额外的项目要添加到此讨论中:

      1. 在没有为此类事件适当准备的情况下终止创建 UI 控件的线程 (Thread.Abort()) 可能会导致内存被预期使用。

      2. 通过 Pinvoke 访问非托管资源而不清理它们可能会导致内存泄漏。

      3. 修改大字符串对象。不一定是内存泄漏,一旦超出范围,GC 会处理它,但是,在性能方面,如果经常修改大字符串,您的系统可能会受到影响,因为您不能真正依赖 GC 来确保程序的足迹是最小。

      4. 经常创建 GDI 对象以执行自定义绘图。如果经常执行 GDI 工作,请重用单个 gdi 对象。

      【讨论】:

        【解决方案7】:

        为了防止 .NET 内存泄漏:

        1) 每当创建具有“IDisposable”接口的对象时,都使用“using”构造(或“try-finally”构造)。

        2) 如果类创建线程或将对象添加到静态或长期存在的集合,则将它们设为“IDisposable”。记住 C# 的“事件”是一个集合。

        这里有一篇关于Tips to Prevent Memory Leaks的短文。

        【讨论】:

          【解决方案8】:

          对我来说真正出乎意料的是:

          Region oldClip = graphics.Clip;
          using (Region newClip = new Region(...))
          {
              graphics.Clip = newClip;
              // draw something
              graphics.Clip = oldClip;
          }
          

          内存泄漏在哪里?对,你也应该处置oldClip!因为Graphics.Clip 是罕见的属性之一,它每次调用 getter 时都会返回一个新的一次性对象。

          【讨论】:

            【解决方案9】:
            1. 保留对不再需要的对象的引用。

            关于其他 cmets - 确保调用 Dispose 的一种方法是在代码结构允许时使用 using...。

            【讨论】:

              【解决方案10】:

              直接设置 GridControl.DataSource 属性,而不使用 BindingSource 类 (http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx) 的实例。

              这导致我的应用程序出现泄漏,我花了很长时间才使用分析器进行追踪,最终我发现了 Microsoft 回复的这个错误报告:http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=92260

              有趣的是,在 BindingSource 类的文档中,Microsoft 试图将其作为一个经过深思熟虑的合法类传递出去,但我认为他们创建它只是为了解决有关货币管理器和将数据绑定到网格控件的基本泄漏问题。

              当心这个,我敢打赌,因为这个原因,那里绝对有大量泄漏的应用程序!

              【讨论】:

              • 恶意帖子。我们有一个移动应用程序,我们一直在努力清理资源并确保实现 IDisposable 的所有内容都得到处理。即使在所有这些之后,我们仍然在大量使用后在现场发生了奇怪的崩溃......我们遇到了这个确切的场景!你摇滚。
              • 对 Mat Nadrofsky 的评论大声笑 +1
              【解决方案11】:

              死锁线程永远不会释放根。显然,您可以争辩说僵局带来了更大的问题。

              一个死锁的终结器线程将阻止所有剩余的终结器运行,从而阻止所有可终结的对象被回收(因为它们仍然被 freachable 列表作为根)。

              在多 CPU 机器上,您可以比终结器线程运行终结器更快地创建可终结对象。只要这种情况持续下去,您就会“泄漏”内存。这可能不太可能在野外发生,但很容易复制。

              大对象堆没有被压缩,所以你可能会通过碎片泄漏内存。

              有许多对象必须手动释放。例如。远程处理没有租约和程序集的对象(必须卸载 AppDomain)。

              【讨论】:

                【解决方案12】:

                Finalize(或来自 Finaliser 的 Dispose 调用)方法中的异常会阻止非托管资源被正确释放。 一个常见的原因是程序员假设将释放什么订单对象并尝试释放已经释放的对等对象,从而导致异常,而 Finalize/Dispose 方法的其余部分没有被释放调用。

                【讨论】:

                  【解决方案13】:

                  每次调用 IDisposable 是最容易开始的地方,而且绝对是抓住代码库中所有低悬的内存泄漏果实的有效方法。然而,这并不总是足够的。例如,了解托管代码在运行时生成的方式和时间也很重要,并且一旦程序集加载到应用程序域中,它们就永远不会被卸载,这会增加应用程序的占用空间。

                  【讨论】:

                  • 没办法。在 .Net 中的 Compact Framework 上进行开发,如果您没有正确处理对象,您很快就会发现您的设备会很快耗尽内存,因此您不应该在这里进行概括。你不能等待 GC 去做。
                  【解决方案14】:

                  在非托管语言中可能导致内存泄漏的许多事情仍然可能导致托管语言中的内存泄漏。例如,bad caching policies 可能会导致内存泄漏。

                  但正如格雷格和丹尼所说,没有完整的清单。任何可能导致在其有用生命周期后保留内存的东西都可能导致泄漏。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2011-06-28
                    • 2013-03-29
                    • 2010-11-28
                    • 2018-08-26
                    • 2013-07-12
                    • 2012-09-10
                    • 1970-01-01
                    • 2012-10-20
                    相关资源
                    最近更新 更多