解读"记忆泄漏"

huseyint 165 .net performance memory-leaks com-interop

在.NET透视图中:

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

Karl Seguin.. 110

我见过的最好的解释是在免费的编程基础电子书的第7章.

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

当你开始获得OutOfMemoryExceptions或你的内存使用量超出你的期望时,你会知道你有泄漏(PerfMon有很好的内存计数器).

了解.NET的内存模型是避免它的最好方法.具体来说,了解垃圾收集器的工作原理以及引用的工作方式 - 再次,我将您介绍到电子书的第7章.另外,要注意常见的陷阱,可能是最常见的事件.如果对象A被注册到对象B上的事件,则对象A将一直存在,直到对象B消失,因为B持有对A的引用.解决方案是在完成后取消注册您的活动.

当然,一个好的内存配置文件可以让你看到你的对象图并探索你的对象的嵌套/引用,看看引用来自哪里以及root对象负责什么(红门蚂蚁配置文件,JetBrains dotMemory,memprofiler真的很好选择,或者你可以使用纯文本的WinDbgSOS,但我强烈推荐商业/视觉产品,除非你是一个真正的大师).

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

  • 哦,你喜欢书吗?我看到作者不时在stackoverflow上弹出. (11认同)
  • @Andrei:我认为正在运行的Thread可能是一个对象生成本身的最好例子.将对自身的引用放在静态非公共位置(例如订阅静态事件,或通过静态字段初始化实现单例)的对象也可以自己扎根,因为没有明显的方法来... ......从它的系泊中"拔掉"它. (2认同)

Coincoin.. 34

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

"不再使用"具有多个含义,它可能意味着"不再引用它",即完全不可恢复,或者它可能意味着,引用,可恢复,未使用但程序仍保留引用.只有后者适用于.Net才能完美管理对象.但是,并非所有类都是完美的,并且在某些时候,潜在的非托管实现可能会永久性地泄漏该进程的资源.

在所有情况下,应用程序消耗的内存超过严格需要的内存.根据泄漏的数量,边的影响可以从无,从过度收集导致的减速,到一系列内存异常,最后是强制错误,然后强制进程终止.

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

对于大多数泄漏,在进程终止时恢复资源,但是在某些精确的情况下并不总是恢复某些资源,GDI游标句柄因此而臭名昭着.当然,如果您有进程间通信机制,则在该进程释放或终止之前,不会释放在另一进程中分配的内存.


Martin.. 29

我认为"什么是内存泄漏"和"有什么影响"的问题已经得到了很好的回答,但我想在其他问题上添加更多内容......

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

一个有趣的方法是打开perfmon并为所有堆#Gen 2集合中的#字节添加跟踪,在每种情况下只查看您的进程.如果执行特定功能导致总字节数增加,并且在下一代第2代集合后仍保留分配内存,则可能会说该功能会泄漏内存.

如何预防

其他好的意见已经给出.我只想补充一点,也许最常被忽视的.NET内存泄漏原因是将事件处理程序添加到对象而不删除它们.附加到对象的事件处理程序是对该对象的引用形式,因此即使在所有其他引用都已消失之后也会阻止收集.始终记得分离事件处理程序(使用-=C#中的语法).

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

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


John.. 19

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

我个人遭受了内存不足的异常,这可能是由于网络应用程序中的内存泄漏所致.(OOM也可以来自固定看Pinning Artical).如果您没有收到OOM错误或需要确认是否是导致它的内存泄漏,那么唯一的方法是分析您的应用程序.

我也会尝试确保以下内容:

a)实现Idisposable的所有东西都是使用finally块或者using语句来处理的,这些包括画笔,笔等等(有些人认为除此之外什么都没有)

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

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

希望这可以帮助.


Eric Z Beard.. 19

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

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

这些文章描述了如何创建流程的内存转储以及如何对其进行分析,以便您可以首先确定泄漏是否是非托管或托管的,如果管理它,如何确定它的来源.

Microsoft还有一个更新的工具来帮助生成故障转储,以替换名为DebugDiag的ADPlus.

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


Nick.. 15

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


Gus Paul.. 15

关于垃圾收集器如何工作的最佳解释是在Jeff Richters CLR中通过C# book,(第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的研讨会得到了这个提示).


Bernard.. 14

我想在一个托管环境中,泄漏就是你不必要地引用一大块内存.


neslekkiM.. 11

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

内存泄漏是指您附加到资源但不放弃它.您可以在托管和非托管编码中执行此操作.

关于.NET和其他编程工具,有关于垃圾收集的想法,以及其他最小化会导致应用程序泄漏的情况的方法.但防止内存泄漏的最佳方法是,您需要在所使用的平台上了解底层内存模型以及工作原理.

相信GC和其他魔法会清理你的混乱是内存泄漏的简短方法,并且以后很难找到.

如果编码不受管理,您通常会确保清理,您知道您所拥有的资源将是您清理的责任,而不是清洁工.

另一方面,在.NET中,很多人认为GC会清理所有内容.嗯,它为你做了一些,但你需要确保它是如此..NET确实包含很多东西,所以你并不总是知道你是在处理托管资源还是非托管资源,而是需要确定你正在处理什么.处理字体,GDI资源,活动目录,数据库等通常是您需要注意的事项.

在管理方面,我会把我的脖子放在线上,说它一旦杀死/移除过程就会消失.

我看到很多人都有这个,我真的希望这会结束.你不能要求用户终止你的应用程序来清理你的烂摊子!看看浏览器,可以是IE,FF等,然后打开,比如谷歌阅读器,让它停留几天,看看会发生什么.

如果您随后在浏览器中打开另一个选项卡,浏览某个站点,然后关闭托管导致浏览器泄漏的其他页面的选项卡,您认为浏览器是否会释放内存?IE不是这样.在我的电脑上,如果我使用谷歌阅读器,IE将在很短的时间内(大约3-4天)轻松吃掉1 GiB的内存.有些新闻报道甚至更糟.


Pat.. 10

我将同意伯纳德关于.net内存泄漏的内容.

您可以分析您的应用程序以查看其内存使用情况,并确定如果它不应该管理大量内存,您可以说它有泄漏.

在管理方面,我会把我的脖子放在线上,说它一旦杀死/移除过程就会消失.

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


Seibar.. 10

我想在一个托管环境中,泄漏就是你不必要地引用一大块内存.

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

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

如果您创建一个使用非托管对象的类,如果您没有正确实现IDisposable,则可能会导致类的用户出现内存泄漏.


Josh.. 9

所有内存泄漏都通过程序终止来解决.

内存泄漏,操作系统可能会决定代表您解决问题.


Joel Lucsy.. 7

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

如果要创建大型内存结构(字典或列表),那么谨慎查找确切规则是什么.

至于在进程终止时回收内存,除非你运行Win98或它的等价物,否则一切都会在终止时释放回操作系统.唯一的例外是跨进程打开的事情,另一个进程仍然打开资源.

COM对象可能很棘手.如果你总是使用这种IDispose模式,你就会安全.但是我遇到了一些实现的互操作程序集IDispose.这里的关键是Marshal.ReleaseCOMObject当你完成它时打电话.COM对象仍然使用标准COM引用计数.


Lars Truijen.. 6

当在.Net中发现内存泄漏时,我发现.Net Memory Profiler非常有用.它不像Microsoft CLR Profiler那样免费,但在我看来更快,更重要.一个