"内存泄漏"的剖析
在.NET透视图中:
什么是内存泄漏?我们如何确定应用程序是否泄漏?有什么影响?如何防止内存泄漏?如果应用程序存在内存泄漏,则在进程退出或者被杀死时会消失吗?还是即使进程完成后,应用程序中的内存泄漏也会影响系统上的其他进程吗?通过COM Interop和/或者P / Invoke访问的非托管代码又如何呢?
解决方案:
我猜在托管环境中,泄漏是我们保留不必要的对周围大块内存的引用。
对于.net中的内存泄漏,我将与Bernard保持一致。
我们可以分析应用程序以查看其内存使用情况,并确定是否在不应该使用的情况下管理大量内存,则可以说它存在泄漏。
用管理的术语讲,一旦进程被终止/删除,它确实会消失。
非托管代码是它自己的野兽,如果其中存在泄漏,它将遵循标准的内存。泄漏定义。
我所见过的最好的解释是在免费的《编程基础》电子书的第7章中。
基本上,在.NET中,当引用的对象植根时会发生内存泄漏,因此无法进行垃圾回收。当我们保留超出预期范围的引用时,会意外发生。
我们将知道开始获取OutOfMemoryExceptions时发生泄漏,或者内存使用量超出了预期(PerfMon具有不错的内存计数器)。
了解.NET的内存模型是避免它的最佳方法。具体来说,在了解了垃圾收集器如何工作以及引用如何再次工作之后,我将我们引向电子书的第7章。另外,请注意常见的陷阱,可能是最常见的事件。如果将对象A注册到对象B上的事件,则对象A会一直存在,直到对象B消失为止,因为B拥有对A的引用。## 解决方案是在完成操作后注销事件。
当然,良好的内存配置文件将使我们能够查看对象图并浏览对象的嵌套/引用,以查看引用来自何处以及由哪个根对象负责(red-gate蚂蚁配置文件,JetBrains dotMemory,memprofiler确实很棒选择,或者我们可以使用纯文本WinDbg和SOS,但是除非我们是真正的专家,否则我强烈建议我们使用商业/视觉产品)。
我相信非托管代码会遭受其典型的内存泄漏,除了共享引用是由垃圾收集器管理之外。最后一点我可能是错的。
严格来说,内存泄漏正在消耗程序"不再使用"的内存。
"不再使用"的含义不只一种,可能意味着"不再引用",即完全不可恢复,或者可能意味着已引用,可恢复,未使用,但程序仍会保留这些引用。只有后者适用于.Net才能完美管理对象。但是,并非所有类都是完美的,在某个时刻,底层的不受管实现可能会永久泄漏该进程的资源。
在所有情况下,应用程序消耗的内存都比严格需要的更多。副作用(取决于泄漏的数量)可能从无到由过度收集导致的放慢,到一系列内存异常,最后是致命错误,然后强制终止进程。
当监视显示在每个垃圾回收周期之后,越来越多的内存分配给进程时,我们知道应用程序存在内存问题。在这种情况下,我们或者在内存中保留了过多的内存,或者某些底层的非托管实现正在泄漏。
对于大多数泄漏,资源是在过程终止时恢复的,但是在某些精确的情况下某些资源并非总是可以恢复的,因此GDI游标句柄是众所周知的。当然,如果我们具有进程间通信机制,则在该进程释放它或者终止该进程之前,不会释放在其他进程中分配的内存。
我将内存泄漏定义为一个对象,该对象在完成后不会释放所有分配的内存。我发现如果我们使用Windows API和COM(即其中包含错误或者未正确管理的非托管代码),框架和第三方组件,则可能会在应用程序中发生这种情况。我还发现在使用某些物体(例如笔)后不整理可能会引起问题。
我个人遭受了内存不足异常的影响,这可能是由于内存异常引起的,但并非仅限于点网应用程序中的内存泄漏。 (OOM也可以来自固定,请参见固定艺术)。如果我们没有收到OOM错误,或者需要确认是否是导致此问题的内存泄漏,那么唯一的方法就是分析应用程序。
我还将尝试确保以下几点:
a)实现Idisposable的所有内容都可以通过使用finally块或者using语句(包括画笔,钢笔等)进行处理(有些人争辩说,将所有内容都设置为空)
b)使用finally或者using语句再次关闭具有close方法的任何内容(尽管我发现using并不总是关闭,具体取决于我们是否在using语句之外声明了对象)
c)如果我们使用的是非托管代码/ Windows API,则这些代码/窗口API将在之后正确处理。 (有些有清理方法来释放资源)
希望这可以帮助。
通过程序终止解决所有内存泄漏。
内存不足,操作系统可能会决定代表我们解决问题。
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():
StreamReader sr; using(sr = new StreamReader("somefile.txt")) { //do some stuff }
而且,如果创建使用非托管对象的类,并且未正确实现IDisposable,则可能导致类用户的内存泄漏。
还请记住,.NET有两个堆,一个是大对象堆。我相信大约85k或者更大的对象放在此堆上。该堆的生存规则与常规堆不同。
如果要创建大型内存结构(词典或者列表的内存),则应谨慎查找确切的规则。
至于在进程终止时回收内存,除非我们运行的是Win98或者同等版本,否则一切都会在终止时释放回OS。唯一的例外是跨进程打开的事物,而另一个进程仍具有打开的资源。
COM对象可能很棘手。如果我们始终使用" IDispose"模式,那将是安全的。但是我遇到了一些实现IDispose
的互操作程序集。完成后,这里的关键是调用Marshal.ReleaseCOMObject
。 COM对象仍然使用标准的COM参考计数。
如果我们需要诊断.NET中的内存泄漏,请检查以下链接:
http://msdn.microsoft.com/zh-CN/magazine/cc163833.aspx
http://msdn.microsoft.com/zh-CN/magazine/cc164138.aspx
这些文章描述了如何创建进程的内存转储以及如何对其进行分析,以便我们可以首先确定泄漏是不受管理的还是受管理的,以及是否可以管理泄漏,以及如何确定泄漏的来源。
Microsoft还拥有一个更新的工具来协助生成故障转储,以取代称为DebugDiag的ADPlus。
http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en
我认为"什么是内存泄漏"和"什么是影响"问题已经很好地回答了,但是我想在其他问题上再添加一些内容...
如何了解应用程序是否泄漏
一种有趣的方法是打开perfmon并添加所有堆和Gen 2集合中字节的跟踪,在每种情况下仅查看进程。如果行使一项特定功能导致总字节数增加,并且该内存在下一个Gen 2收集之后仍保持分配状态,则我们可能会说该功能泄漏了内存。
如何预防
还给出了其他好的意见。我只想补充说一下,.NET内存泄漏的最常见的原因可能是在不删除对象的情况下向对象添加了事件处理程序。添加到对象的事件处理程序是对该对象的一种引用形式,因此即使在所有其他引用都消失之后,它也会阻止收集。永远记住要分离事件处理程序(在C#中使用-=
语法)。
当进程退出时,泄漏会消失吗?COM互操作又如何呢?
当进程退出时,操作系统将回收映射到其地址空间的所有内存,包括从DLL提供服务的任何COM对象。比较少的是,COM对象可以从单独的进程提供服务。在这种情况下,当进程退出时,我们可能仍要负责所使用的任何COM服务器进程中分配的内存。
使用Microsoft的CLR Profiler http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en是确定哪些对象正在保存内存,什么执行流程会导致内存丢失的好方法创建这些对象,并监视哪些对象位于堆中的哪个位置(碎片,LOH等)。
当发现.Net中的内存泄漏时,我发现.Net Memory Profiler是一个很好的帮助。它不是像Microsoft CLR Profiler一样免费,但是我认为它更快,更重要。一种
关于垃圾收集器工作原理的最好解释是通过Cbook的Jeff Richters CLR(第20章)。读这篇文章为理解对象如何持久化提供了很好的基础。
导致对象意外扎根的最常见原因之一是通过连接超出类的事件。如果我们挂了一个外部事件
例如
SomeExternalClass.Changed += new EventHandler(HandleIt);
并在处理时忘记解开它,那么SomeExternalClass会引用类。
如上所述,SciTech内存分析器非常适合向我们显示怀疑泄漏的对象的根源。
但是,还有一种非常快速的方法来检查特定类型,即只使用WnDBG(甚至可以在连接时在VS.NET即时窗口中使用它):
.loadby sos mscorwks !dumpheap -stat -type <TypeName>
现在做一些我们认为会处理该类型对象的事情(例如,关闭窗口)。在这里很方便,在某个地方有一个调试按钮可以多次运行System.GC.Collect()。
然后再次运行!dumpheap -stat -type <TypeName>
。如果这个数字没有下降,或者没有下降到我们期望的水平,那么我们就有了进行进一步调查的基础。
(我从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阅读器,则IE会在很短的时间内(大约3-4天)轻松占用1 GiB的内存。一些新闻页甚至更糟。