关于.net:“内存泄漏”的剖析

关于.net:“内存泄漏”的剖析

Anatomy of a “Memory Leak”

在.NET透视图中:

  • 什么是内存泄漏?
  • 您如何确定您的应用程序是否泄漏? 有什么影响?
  • 如何防止内存泄漏?
  • 如果您的应用程序存在内存泄漏,则在进程退出或被杀死时会消失吗? 还是即使进程完成后,应用程序中的内存泄漏也会影响系统上的其他进程吗?
  • 通过COM Interop和/或P / Invoke访问的非托管代码又如何呢?

我所见过的最好的解释是在免费的《编程基础》电子书的第7章中。

基本上,在.NET中,当引用的对象植根时会发生内存泄漏,因此无法进行垃圾回收。当您保留超出预期范围的引用时,会意外发生。

您将知道开始获取OutOfMemoryExceptions时发生泄漏,或者您的内存使用量超出了您的期望(PerfMon具有不错的内存计数器)。

了解.NET的内存模型是避免它的最佳方法。具体而言,了解垃圾收集器的工作方式和引用的工作方式-再次,我将您引向电子书的第7章。另外,请注意常见的陷阱,可能是最常见的事件。如果将对象A注册到对象B上的事件,则对象A将一直存在,直到对象B消失为止,因为B拥有对A的引用。解决方案是在完成操作后注销事件。

当然,良好的内存配置文件将使您能够查看对象图并探索对象的嵌套/引用,以查看引用来自何处以及由哪个根对象负责(red-gate蚂蚁配置文件,JetBrains dotMemory,memprofiler确实不错选择,或者您可以使用纯文本的WinDbg和SOS,但是除非您是真正的专家,否则我强烈建议您使用商业/视觉产品)。

我相信非托管代码会受到其典型的内存泄漏的影响,只是共享引用由垃圾收集器管理。最后一点我可能是错的。


严格来说,内存泄漏正在消耗程序"不再使用"的内存。

"不再使用"的含义不只一种,可能意味着"不再引用",即完全不可恢复,或者可能意味着已引用,可恢复,未使用,但程序仍会保留引用。只有后者适用于.Net才能完美管理对象。但是,并非所有类都是完美的,在某个时刻,底层的不受管实现可能会永久泄漏该进程的资源。

在所有情况下,应用程序消耗的内存都超过了严格要求。副作用(取决于泄漏的数量)可能从无到由过度收集导致的减慢,到一系列内存异常,最后是致命错误,然后强制终止进程。

当监视显示在每个垃圾回收周期之后,越来越多的内存分配给您的进程时,您知道应用程序存在内存问题。在这种情况下,您要么在内存中保留了太多内存,要么某些底层的非托管实现正在泄漏。

对于大多数泄漏,资源是在过程终止时恢复的,但是在某些精确的情况下,某些资源并不总是可以恢复的,为此,GDI游标句柄是众所周知的。当然,如果您具有进程间通信机制,则在该进程将其释放或终止之前,不会释放在其他进程中分配的内存。


我认为"什么是内存泄漏"和"什么是影响"问题已经很好地回答了,但是我想在其他问题上再添加一些内容...

如何了解您的应用程序是否泄漏

一种有趣的方法是打开perfmon并为所有堆和#Gen 2集合中的#个字节添加跟踪,在每种情况下仅查看您的进程。如果行使一项特定功能导致总字节数增加,并且该内存在下一次??第二代收集之后仍保持分配状态,则您可能会说该功能泄漏了内存。

如何预防

还给出了其他好的意见。我只想补充说一下,.NET内存泄漏的最常见的原因可能是在不删除对象的情况下向对象添加了事件处理程序。附加到对象的事件处理程序是对该对象的一种引用形式,因此即使在所有其他引用都消失之后,它也会阻止收集。始终记住要分离事件处理程序(在C#中使用-=语法)。

当进程退出时,泄漏会消失吗?COM互操作又如何呢?

当进程退出时,操作系统将回收映射到其地址空间的所有内存,包括从DLL提供服务的任何COM对象。比较少的是,COM对象可以从单独的进程提供服务。在这种情况下,当您的进程退出时,您可能仍要负责所使用的任何COM服务器进程中分配的内存。


如果您需要诊断.NET中的内存泄漏,请检查以下链接:

http://msdn.microsoft.com/zh-CN/magazine/cc163833.aspx

http://msdn.microsoft.com/zh-CN/magazine/cc164138.aspx

这些文章描述了如何创建进程的内存转储以及如何对其进行分析,以便您可以首先确定泄漏是不受管理的还是受管理的,以及如果泄漏得到管理,如何确定泄漏的来源。

Microsoft还拥有一个更新的工具来帮助生成故障转储,以代替ADPlus,称为DebugDiag。

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en


我将内存泄漏定义为一个对象,该对象在完成后不会释放所有分配的内存。我发现如果您使用Windows API和COM(即其中包含错误或未正确管理的非托管代码),框架和第三方组件,则可能会在您的应用程序中发生这种情况。我还发现在使用某些物体(例如笔)后不整理可能会引起问题。

我个人遭受了内存不足异常的影响,这可能是由于内存异常引起的,但并非仅限于点网应用程序中的内存泄漏。 (OOM也可以来自固定,请参见固定艺术)。如果您没有收到OOM错误,或者需要确认是否是导致此问题的内存泄漏,那么唯一的方法就是分析您的应用程序。

我还将尝试确保以下几点:

a)实现Idisposable的所有内容都使用finally块或using语句(包括画笔,钢笔等)进行处置(有些人争辩说,将所有内容都设置为空)

b)使用finally或using语句再次关闭具有close方法的任何内容(尽管我发现using并不总是关闭,具体取决于您是否在using语句之外声明了该对象)

c)如果您使用的是非托管代码/ Windows API,则这些代码/窗口API将在之后正确处理。 (有些有清理方法来释放资源)

希望这可以帮助。


使用Microsoft的CLR Profiler http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en是确定哪些对象正在保存内存,什么执行流程会导致内存丢失的好方法创建这些对象,并监视哪些对象位于堆中的哪个位置(碎片,LOH等)。


关于垃圾收集器工作原理的最好解释是通过C#书(第20章)在Jeff Richters CLR中进行的。阅读此书将为理解对象如何持久化提供良好的基础。

导致对象意外扎根的最常见原因之一是将事件挂接到类之外。如果您挂了一个外部事件

例如

1
SomeExternalClass.Changed += new EventHandler(HandleIt);

并在处理时忘记解开它,那么SomeExternalClass会引用您的类。

如上所述,SciTech内存分析器非常适合向您显示怀疑泄漏的对象的根。

但是,还有一种非常快速的方法来检查特定类型,即只使用WnDBG(甚至可以在连接时在VS.NET即时窗口中使用它):

1
2
.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

现在做一些您认为会处理该类型对象的事情(例如,关闭窗口)。在这里方便使用在某处将运行System.GC.Collect()几次的调试按钮。

然后再次运行!dumpheap -stat -type 。如果这个数字没有下降,或者没有下降到您期望的水平,那么您就有了进行进一步调查的基础。
(我从Ingo Rammer举办的研讨会中获得了这个技巧)。


我猜在托管环境中,泄漏就是您不必要地引用周围的大量内存。


人们为什么认为.NET中的内存泄漏与其他任何泄漏都不相同?

内存泄漏是当您附加到资源并且不让它消失时。您可以在托管和非托管编码中执行此操作。

关于.NET和其他编程工具,已经有关于垃圾收集的想法,以及其他使情况最小化的方法,这些情况会使您的应用程序泄漏。
但是,防止内存泄漏的最佳方法是,您需要了解所使用平台上的基础内存模型以及其工作方式。

相信GC和其他魔术可以清除混乱,这是内存泄漏的捷径,以后很难找到。

在对非托管代码进行编码时,通常会确保清理,您知道所拥有的资源将是清理的责任,而不是看门人的责任。

另一方面,在.NET中,许多人认为GC可以清理所有内容。好吧,它为您做了一些,但是您需要确保是这样。 .NET确实包装了很多东西,因此您并不总是知道您要处理的是托管资源还是非托管资源,因此需要确定要处理的内容。处理字体,GDI资源,活动目录,数据库等通常是您需要注意的事情。

In managed terms I will put my neck on
the line to say it does go away once
the process is killed/removed.

我看到很多人都这样做了,我真的希望这会结束。您不能要求用户终止您的应用程序以清理混乱!
看一下可以是IE,FF等的浏览器,然后打开例如Google Reader,让它停留几天,然后看看会发生什么。

如果然后在浏览器中打开另一个选项卡,浏览到某个站点,然后关闭承载导致浏览器泄漏的其他页面的选项卡,您是否认为浏览器会释放内存? IE并非如此。如果我使用Google阅读器,则在很短的时间内(大约3-4天),IE会在我的计算机上轻松占用1 GiB的内存。一些新闻页甚至更糟。


I guess in a managed environment, a
leak would be you keeping an
unnecessary reference to a large chunk
of memory around.

绝对。另外,在适当的情况下不对一次性对象使用.Dispose()方法可能会导致内存泄漏。最简单的方法是使用using块,因为它最后自动执行.Dispose():

1
2
3
4
5
StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

而且,如果您创建的类使用的是非托管对象,那么如果您未正确实现IDisposable,则可能导致类用户的内存泄漏。


通过程序终止解决所有内存泄漏。

内存不足,操作系统可能会决定代表您解决问题。


对于.net中的内存泄漏,我将与Bernard保持一致。

您可以分析应用程序以查看其内存使用情况,并确定是否在不应该使用的情况下管理大量内存,则可以说它存在泄漏。

用管理的术语讲,一旦进程被终止/删除,它确实会消失。

非托管代码是它自己的野兽,如果其中存在泄漏,它将遵循标准的内存。泄漏定义。


还请记住,.NET有两个堆,一个是大对象堆。我相信大约85k或更大的对象放在此堆上。该堆的生存期规则与常规堆不同。

如果您要创建大型内存结构(词典或列表的内存结构),请谨慎查找确切的规则。

至于在进程终止时回收内存,除非您运行的是Win98或等同版本,否则所有内容都将在终止时释放回OS。唯一的例外是跨进程打开的事物,而另一个进程仍具有打开的资源。

COM对象可能很棘手。如果您始终使用IDispose模式,则将是安全的。但是我遇到了一些实现IDispose的互操作程序集。完成后,这里的关键是调用Marshal.ReleaseCOMObject。 COM对象仍然使用标准的COM参考计数。


当发现.Net中的内存泄漏时,我发现.Net Memory Profiler是一个很好的帮助。它不是像Microsoft CLR Profiler一样免费,但是我认为它更快,更重要。一种


一个定义是:无法释放无法访问的内存,该内存在执行分配过程期间无法再分配给新进程。大多数情况下,可以使用GC技术对其进行固化,也可以通过自动化工具进行检测。

有关更多信息,请访问http://all-about-java-and-weblogic-server.blogspot.in/2014/01/what-is-memory-leak-in-java.html。


推荐阅读