Welcome

首页 / 软件开发 / .NET编程技术 / .NET托管内存类应用的内存泄漏分析和诊断

.NET托管内存类应用的内存泄漏分析和诊断2011-02-26 javaeye lzy.je在托管内存管理中,“泄漏”意义不同与传统 Native 应用中的忘记显式释放(delete/delete[] 等)不同,当然对于非托管资源之类(如句柄等)还是需要在 Finalize (析构方法等同于 Finalize)方法中显式释放的,在托管内存管理中“泄漏”对象实例指的是,由于与 Root 对象集中的对象存在本应断开的引用关系,而让 GC 线程认为该对象还被使用,因而不能被释放,尽管其不再会被使用。决大部分情况下,由于应用(程序员)认为该对象不会存在了,而在再次使用时,又在托管堆中再次创建了该对象实例,可以想象这样的后果很严重,随着创建次数增加堆内存会爆满。(托管堆中 G3 区爆满,G2 区无法腾出空间)。

GC 判断一个对象是否可以被释放是通过从被称为 Root 对象集中的根对象开始(如 Main 函数的 args 形参、static 变量及其对象成员等),遍历出所有被其引用的对象和子对象。GC 执行时通过标记这些引用中的对象,清除未标记上的对象来完成内存释放(标记、清理算法),当然清除也可能分步(如移送 Finalize 队列等)。由于标记、清理算法的中断时间等性能考虑,托管堆会分区(代),当前 CLR 是 3 代 – G1、G2、G3。伴随 Age(GC 一次 Age 加 1)增加,对象会逐渐从 G1 移送到 G3 代中(复制、整理算法),即 G1 是新生代,都是些短期对象,G3 是老年对象的永久居留地。需要说明的是,实际上在当前版本的 .NET CLR 中有 2 个托管堆(SOH 和 LOH),其中一个叫大对象托管堆(LOH),专门用来存放大于 84, 999 Bytes 的对象。程序只能在 SOH G1 和 LOH 中分配对象空间,只有 CLR GC 线程可以在 SOH 的 G2、G3 中分配(移送)对象。

明白上面的基本道理,下面看看和托管对象实例内存泄漏的图例:

上面图中表示的意思是使用一段时间后,堆中对象与 Root 对象的引用关系,其中颜色由浅到深表示了 Age 的因素。如果此时,GC 线程执行,堆情况将如下所示:

其中所有 Unreachable 的对象实例都将被 GC 所释放,这样托管堆内存会被正确回收。但需要说明的是,如果在 Reachable 的区域中(这部分 GC 是不会释放的),有一些被引用的对象在以后不会再使用,而且应用(程序员)在下次使用时还会创建新的对象时“泄漏”就发生了。当涉及对此类对象创建操作的业务被用户反复执行后,CLR 的 G3 代托管堆段会逐渐增长,服务的死期也就不远了。