1 概念
内存泄漏(Memory Leak)是指应用程序片断使用了某块内存,但是用完之后一直未释放,从而造成你不用了,别人也用不了的浪费情况。内存总有上限,当这样的浪费越来越多的时候,就会逐渐耗尽内存,从而引起程序或系统崩溃。
当应用程序引发异常OutOfMemoryException,或者内存使用率一直上升,则应用程序极有可能正在发生内存泄漏。
2 原因
.NET 使用垃圾回收器(GC)来自动管理内存,但它却不是万能的。因为.NET 应用程序的内存类型可分为堆栈、托管堆和非托管堆,而GC只负责管理托管堆,其它两种类型则管不了。即便这样,由于编程错误也有可能妨碍GC的正常工作,使托管堆也出现内存泄漏。
这样,内存泄漏就可分为三种类型:
堆栈内存泄漏——堆栈用于存储局部变量、方法参数、返回值和其他临时值等。堆栈为方法调用所预留的空间并不由垃圾收集器处理,而是在方法返回时自动清理。那么发生堆栈泄漏的一种情况就是进行一种极其耗费堆栈资源并且从不返回的方法调用,从而使关联的堆栈无法得到释放。另一种情况是线程泄漏。堆栈是按线程进行分配的,每个线程都有自己对应的堆栈。如果应用程序不停地创建新线程,而却忘了正常终止这些线程,则可引起线程泄漏。
非托管堆泄漏——非托管堆用于存储包装操作系统资源的对象,常见的有文件句柄、窗口句柄或网络资源,如操作Win API, Excel文件,数据库连接,网络连接等。发生非托管堆泄漏的主要原因是未能正确释放非托管对象,比如忘了释放或终结器被终止。
托管堆泄漏——托管堆用于分配CLR上的对象,由垃圾收集器管理。此处泄漏的最多原因是垃圾收集器不能断定某个对象为垃圾,导致不能回收内存。比如不断地往一个集合添加Item,而程序一直保留着对该集合的引用。其它泄漏原因还有大型对象堆碎片。
3 分析
真正分析内存泄漏的问题是比较困难的,可能要借助专门的调试工具,如WinDbg。但是,首先可以通过简单判断上述内存泄漏的三种类型来缩小范围。使用 PerfMon 来检查用于应用程序的下列性能计数器:
- Process/Private Bytes
- .NET CLR Memory/# Bytes in All Heaps
- .NET CLR LocksAndThreads/# of current logical Threads
Process/Private Bytes 计数器用于报告系统中专门为某一进程分配而无法与其他进程共享的所有内存。.NET CLR Memory/# Bytes in All Heaps 计数器报告第 0 代、第 1 代、第 2 代和大型对象堆的合计大小。.NET CLR LocksAndThreads/# of current logical Threads 计数器报告 AppDomain 中逻辑线程的数量。
如果应用程序的逻辑线程计数出现意想不到的增大,则表明线程堆栈发生泄漏。如果 Private Bytes 增大,而 # Bytes in All Heaps 保持不变,则表明非托管内存发生泄漏。如果上述两个计数器均有所增加,则表明托管堆中的内存消耗在增长。
4 参考
Identify And Prevent Memory Leaks In Managed Code
Investigating Memory Issues