一 基础知识
在分析之前,先上一张图:
从上面可以看到,这个w3wp进程占用了376M内存,启动了54个线程。
在使用windbg查看之前,看到的进程含有 *32 字样,意思是在64位机器上已32位方式运行w3wp进程。这个可以通过查看IIS Application Pool 的高级选项进行设置:
好了,接下打开Windbg看看这个w3wp进程占用了376M内存,启动的54个线程。
1. 加载 WinDbg SOS 扩展命令
.load C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll
2. !dumpheap -stat
!DumpHeap 将遍历 GC 堆对对象进行分析。
MT Count TotalSize Class Name
78eb9834 1 12 System.ServiceModel.ServiceHostingEnvironment+HostingManager+ExtensionHelper
0118c800 101 14824 Free
...
63ce0004 19841 1111096 System.Reflection.RuntimeMethodInfo
63ce2ee4 11080 2061036 System.Int32[]
63ce0d48 34628 2242596 System.String
63ce37b8 20012 3264884 System.Byte[]
63cb4518 157645 4940676 System.Object[]
Total 524310 objects
可以看到,w3wp上总共有524310个对象, 共占用了这些内存。
我们可以将上述上述列出的这些对象归为2类:
1). 有根对象(在应用程序中对这些对象存在引用)
2). 自从上次垃圾回收之后新创建或无跟对象
要注意的是Free这项:
0118c800 101 14824 Free
这项一般都是GC not yet Compacted的空间或一些堆上分配的禁止GC compacted钉扣对象.
第一栏 : 类型的方法列表 MT(method type for the type)
第二栏:堆上的对象数量
第三栏:所有同类对象的总大小
3. !dumpheap -mt 63ce0d48
查看 63ce0d48 单元的有哪些对象。
4. !do 103b3360
看看103b3360地址的string包含哪些内容
可见,103b3360地址的字符串value="System.Web.UI.PageRequestManager:AsyncPostBackError", 占120bytes. 这个字符串对象包含3个字段,它们的偏移量分别是4,8,12。
5. dd 103b3360
看看103b3360的值
从左往右第一列是地址,而第二列开始则是地址上的数据。
6. !dumpheap -type String -min 100
看看堆上所有大于100字节的字符串。 注意:假如 -min 85000(大于85000字节的字符串或对象将存储在大对象堆上).
二. NET内存泄露分析案例
1 基础认识
.net世界里,GC是负责垃圾回收的,但GC仅仅是回收哪些不可及的对象(无根对象),对于有应用的有根对象,GC对此无能为力。
.net一些内存泄漏的根本原因:
- 使用静态引用
- 未退订的事件-作者认为这是最常见的内存泄漏原因
- 未退订的静态事件
- 未调用Dispose方法
- 使用不彻底的Dispose方法
- 在Windows Forms中对BindingSource的误用
- 未在WorkItem/CAB上调用Remove
一些避免内存泄漏的建议:
- 对象的创建者或拥有者负责销毁对象,而不是使用者
- 当不再需要一个事件订阅者时退订此事件,为确保安全可以在Dispose方法中退订
- 当对象不再触发事件时,应该将对象设为null来移除所有的事件订阅者
- 当模型和视图引用同一个对象时,推荐给视图传递一个此对象的克隆,以防止无法追踪谁在使用哪个对象
- 对系统资源的访问应该包装在using块中,这将在代码执行后强制执行Dispose
对这些做基本了解后,我们将步入正题。
2. 案例分析
先上测试代码:
1 public class LeakTest 2 { 3 private static string leakString; 4 5 public LeakTest() 6 { 7 for (int i = 0; i < 1000; i++) 8 { 9 leakString += "LEAK"; 10 } 11 } 12 13 public string GetRamdonString() 14 { 15 System.Random random = new System.Random(); 16 17 string str = string.Empty; 18 for (int i = 0; i < 25; i++) 19 { 20 str += str + random.Next(100); 21 } 22 return str; 23 } 24 25 public void NoDispose() 26 { 27 string str = GetRamdonString(); 28 29 ZipFile zip = new ZipFile(); 30 zip.AddEntry("a.txt", str); 31 zip.AddEntry("b.txt", str); 32 zip.Save("test.rar"); 33 //zip.Dispose(); 34 } 35 } 36 37 class Program 38 { 39 static void Main(string[] args) 40 { 41 LeakTest leakTest = new LeakTest(); 42 leakTest.NoDispose(); 43 Console.ReadLine(); 44 } 45 }