从 .NET 视角来看:
- 内存泄漏是什么?
- 如何确定您的应用程序是否泄漏?会有哪些影响?
- 如何预防内存泄漏?
- 如果您的应用程序有内存泄漏,进程退出或被终止后问题是否解决?或者在进程结束后,应用程序中的内存泄漏是否对系统上的其他进程产生影响?
- 那么通过 COM 互操作和/或 P/Invoke 访问的非托管代码呢?
从 .NET 视角来看:
严格来说,内存泄漏是程序占用了“不再使用”的内存。
“不再使用”有多种含义,可能意味着“没有引用它”,也就是说,完全无法恢复,或者可能意味着已被引用、可恢复、未使用,但程序仍然保留这些引用。只有后者适用于 .Net 的完美管理对象。然而,并非所有的类都是完美的,在某些情况下,底层的非托管实现可能会永久地泄漏资源。
在所有情况下,应用程序消耗的内存比严格需要的更多。副作用,取决于泄漏的数量,可能从没有到由于过度收集而导致的减速,再到一系列内存异常,最终出现致命错误并强制进程终止。
当监视显示在每个垃圾回收周期后为您的进程分配了越来越多的内存时,您就知道应用程序存在内存问题。在这种情况下,您要么将太多内容保留在内存中,要么底层的非托管实现正在发生泄漏。
对于大多数泄漏,当进程终止时,资源将被恢复,但在某些特定情况下,某些资源并不总是会被恢复,例如GDI光标句柄。当然,如果您有一个进程间通信机制,则在其他进程中分配的内存将在该进程释放或终止之前不会被释放。
我认为关于“什么是内存泄漏”和“影响有哪些”的问题已经有了很好的回答,但我想在其他问题上添加一些东西...
如何判断你的应用程序是否泄漏
一个有趣的方法是打开 perfmon 并为 # bytes in all heaps 和 # Gen 2 collections 添加跟踪,每种情况下只查看您的进程。如果运行某个特定功能会导致总字节数增加,并且该内存在下一个 Gen 2 收集后仍保持分配状态,则可以说该功能存在内存泄漏。
如何预防
其他人已经提出了很好的观点。我只想补充一点,即 .NET 内存泄漏最常被忽视的原因可能是在对象中添加事件处理程序而没有将它们移除。附加到对象的事件处理程序是对该对象的一种引用,因此即使所有其他引用已经消失,它也会阻止收集。始终记得解除事件处理程序(在 C# 中使用 -=
语法)。
进程退出时泄漏是否消失,COM 互操作呢?
当您的进程退出时,操作系统会回收映射到其地址空间的所有内存,包括从 DLL 中提供的任何 COM 对象。相对较少情况下,COM 对象可以从单独的进程中提供。在这种情况下,当您的进程退出时,您可能仍然需要负责使用的任何 COM 服务器进程中分配的内存。
如果你需要在.NET中诊断内存泄漏,请查看以下链接:
http://msdn.microsoft.com/en-us/magazine/cc163833.aspx
http://msdn.microsoft.com/en-us/magazine/cc164138.aspx
那些文章描述了如何创建进程内存转储并分析它,以便首先确定您的泄漏是托管还是非托管的,如果是托管的,如何找出它来自哪里。使用来自微软的CLR Profilerhttp://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en是一个很好的方法,可以确定哪些对象占用了内存,执行流程导致这些对象的创建,并监视哪些对象存在于堆上(碎片,LOH等)。
如何垃圾回收器工作的最好解释在Jeff Richter的CLR via C#书中(第20章)。阅读此书可以很好地理解对象如何持久存在。
意外根据对象的最常见原因之一是通过在类外部连接事件。如果您连接外部事件
例如:
SomeExternalClass.Changed += new EventHandler(HandleIt);
如果你在释放对象时忘记取消挂钩,那么一些外部类仍会引用你的类。
如上所述,SciTech内存分析器非常擅长显示你怀疑正在泄漏的对象的根源。
但也有一种非常快速的方法来检查特定类型,只需要使用WnDBG(甚至可以在连接时使用VS.NET即时窗口):
.loadby sos mscorwks
!dumpheap -stat -type <TypeName>
System.GC.Collect()
几次。然后再次运行!dumpheap -stat -type <TypeName>
。如果数字没有降下来,或者降下来的不如你预期,那么你就有了进一步调查的基础。(我从Ingo Rammer的研讨会上得到了这个提示。)我想在托管环境中,泄漏是指你保留了一个不必要的引用到一个大块内存。
为什么人们认为.NET中的内存泄漏与其他泄漏不同?
内存泄漏是当您附加到资源并不释放时发生的。您可以在托管和非托管编码中都这样做。
关于.NET和其他编程工具,有一些关于垃圾回收和最小化可能导致应用程序泄漏的情况的想法。 但是预防内存泄漏的最佳方法是需要了解底层的内存模型以及在使用的平台上的工作原理。
相信垃圾回收和其他“魔法”将清理您的混乱是造成内存泄漏的捷径,并且难以在以后找到。
在进行非托管编码时,通常会确保清除,您知道您占用的资源将是您的责任来清除,而不是管理员的责任。
另一方面,在.NET中,很多人认为GC会清理所有内容。好吧,它确实为您做了一些事情,但您需要确保是这样的。.NET确实包装了很多东西,因此您并不总是知道自己是否正在处理托管或非托管资源,您需要确定自己正在处理什么。 处理字体、GDI资源、活动目录、数据库等通常是您需要注意的事情。
从托管的角度来看,我会说一旦进程被杀死/移除,内存泄漏就消失了。
我看到很多人都有这种想法,而我真的希望这种情况能够结束。您不能要求用户终止应用程序以清理您的混乱! 看看浏览器,可以是IE、FF等,然后打开Google Reader,让它停留几天,看看会发生什么。
如果您随后在浏览器中打开另一个标签页,浏览到某个站点,然后关闭托管造成浏览器泄漏的其他页面的选项卡,您认为浏览器会释放内存吗?但IE不会。如果我使用Google Reader,在我的计算机上,IE在短时间内(约3-4天)就会轻松吃掉1 GiB的内存。有些新闻网页甚至更糟。
在托管环境中,内存泄漏指的是您保留了一个不必要的对大块内存的引用。
完全正确。此外,当适当时未使用可处理对象的.Dispose()方法也会导致内存泄漏。最简单的方法是使用using块,因为它会自动执行.Dispose():
StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
//do some stuff
}
如果您创建了一个使用非托管对象的类,并且没有正确实现IDisposable,那么您可能会为该类的用户造成内存泄漏。