关于优化:.NET中的内存泄漏

关于优化:.NET中的内存泄漏

Memory leaks in .NET

.NET中出现内存泄漏的所有可能方式有哪些?

我知道两个:

  • 未正确注销事件处理程序/代理。
  • 不在Windows窗体中放置动态子控件:
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 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中没有对某个对象的引用,那么它将在某个时间被垃圾回收。因此,这不是引发内存泄漏的方法。

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

    那么,发生这种内存泄漏的各种可能方式有哪些?


    这并不会真正导致泄漏,只会为GC带来更多工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 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这样的托管环境中,像这样放置一次性组件绝不是什么大问题-这是托管手段的重要组成部分。

    您肯定会放慢您的应用程序。但是,您别无选择。


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

    这导致我的应用程序泄漏,使我花了相当长的时间才能通过探查器进行跟踪,最终我发现此错误报告得到了Microsoft的响应:http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx ?反馈ID = 92260

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

    当心这一点,我敢打赌,绝对有大量的泄漏应用程序在此运行!


    无法提供全面的清单...这很像在问"你怎么弄湿?"

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

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


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

    进一步阅读:http://dotnetdebug.ne??t/2005/06/22/blocked-finalizer-thread/


    Finalize(或从Finaliser进行Dispose调用)方法中的异常,这些异常会阻止正确处理非托管资源。
    一种常见的情况是由于程序员假定要处理的对象顺序不同,并试图释放已被处理的对等对象,从而导致异常,而Finalize方法中的Finalize / Dispose其余部分未被调用。


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

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

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

  • 修改大字符串对象。不一定是内存泄漏,一旦超出范围,GC就会解决它,但是从性能angular来看,如果经常修改大字符串,您的系统可能会受到打击,因为您不能真正依靠GC来确保程序的安全。占地面积很小。

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


  • 为防止.NET内存泄漏:

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

    2)如果类\\'IDisposable \\'创建了线程或将对象添加到静态或长期存在的集合中,则使它们为\\'IDisposable \\'。请记住,C#\\'event \\'是一个集合。

    此处是有关防止内存泄漏的提示的简短文章。


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


    您是在谈论意外的内存使用情况还是实际的泄漏?您列出的两种情况并非完全泄漏;在某些情况下,物体停留的时间比预期的长。

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

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

    编辑2:考虑此问题的另一种方法是始终确保适当释放对对象的外部引用。外部表示代码超出您的控制范围。在任何情况下,您都可以"泄漏"内存。


    Tess Fernandez在有关查找和调试内存泄漏的博客文章方面很棒。
    实验6实验7


    我真正出乎意料的一件事是:

    1
    2
    3
    4
    5
    6
    7
    Region oldClip = graphics.Clip;
    using (Region newClip = new Region(...))
    {
        graphics.Clip = newClip;
        // draw something
        graphics.Clip = oldClip;
    }

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


  • 保留对不再需要的对象的引用。
  • 其他注释-确保调用Dispose的一种方法是在代码结构允许时使用using...。


    死锁的线程永远不会释放根。显然,您可能会认为僵局带来了更大的问题。

    死锁的终结器线程将阻止所有剩余的终结器运行,从而防止收回所有可终结对象(因为它们仍由易碎列表所植)。

    在多CPU计算机上,创建终结对象的速度比终结器线程运行终结器的速度快。只要持续,您将"泄漏"内存。这很可能不会在野外发生,但很容易复制。

    未压缩大对象堆,因此您可能会通过碎片泄漏内存。

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


    许多可能导致非托管语言中的内存泄漏的事情仍然可以导致托管语言中的内存泄漏。例如,不良的缓存策略可能导致内存泄漏。

    但是正如Greg和Danny所说,没有完整的清单。任何可能导致内存在其使用寿命后保留的事情都可能导致泄漏。


    推荐阅读