查看w3wp进程占用的内存及.NET内存泄露,死锁分析

基础知识

在分析之前,先上一张图:

从上面可以看到,这个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

  1. !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)

第二栏:堆上的对象数量

第三栏:所有同类对象的总大小

  1. !dumpheap -mt 63ce0d48

查看 63ce0d48 单元的有哪些对象。

  1. !do 103b3360

看看103b3360地址的string包含哪些内容

可见,103b3360地址的字符串value="System.Web.UI.PageRequestManager:AsyncPostBackError", 占120bytes. 这个字符串对象包含3个字段,它们的偏移量分别是4,8,12。

  1. dd 103b3360

看看103b3360的值

从左往右第一列是地址,而第二列开始则是地址上的数据。

  1. !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. 案例分析

先上测试代码:

View Code

需要说明的是:

这里程序里面定义了一个Static 字符串,及使用了Ionic.Zip 这个Zip压缩包,仅仅是为了模拟内存堆积现象,没有调用zip.Dispose()方法,事实上Ionic.Zip并不会造成内存泄露。

正式开始了:

啊哈,好极了。 运行程序,好家伙,果然很耗费内存! 这么个小程序,吃了287M,并启动了12个线程.

0:005> .load C:\Windows\Microsoft.NET\Framework64\v2.0.50727\sos.dll

0:005> .load C:\Symbols\sosex_64\sosex.dll

0:005> !dumpheap -stat

复制代码
 1 0:012> !dumpheap -stat
 2 PDB symbol for mscorwks.dll not loaded
 3 total 12840 objects
 4 Statistics:
 5               MT    Count    TotalSize Class Name
 6 000007ff001d2248        1           24 System.Collections.Generic.Dictionary`2+ValueCollection[[System.String, mscorlib],[Ionic.Zip.ZipEntry, Ionic.Zip.Reduced]]
 7 000007ff000534f0        1           24 ZipTest.LeakTest
 8 000007fee951e8e8        1           24 System.IO.TextReader+NullTextReader
 9 000007fee94f8198        1           24 System.Security.Cryptography.RNGCryptoServiceProvider

11 ...
43 000007ff001d9130     1041        66624 Ionic.Zlib.DeflateManager+CompressFunc
44 000007fee94d2d40     1023        73656 System.Threading.ExecutionContext
45 000007fee951e038     3075      1387592 System.UInt32[]
46 000007fee951ca10     3179      2450704 System.Int16[]
47 0000000000207800      261     67034512      Free
48 000007fee94d7d90      514    134251544 System.String
49 000007fee94dfdd0      102    138593344 System.Byte[]
50 Total 12840 objects

果然,我们看到了里面有2类大对象,分别占用了134M和138M . 好家伙!

0:005> !dumpheap -mt

复制代码
  1 0:012> !dumpheap -mt 000007fee94dfdd0      
  2          Address               MT     Size
  3...   
 24 00000000026f11f0 000007fee94dfdd0    65560     
 25 0000000002701288 000007fee94dfdd0    65560     
 26 00000000027112a0 000007fee94dfdd0    65592     
 27 0000000002722b50 000007fee94dfdd0    65560     
 28 0000000002752b98 000007fee94dfdd0    65560     
 29 ...    
 47 000000000290ab98 000007fee94dfdd0    65560     
 48 000000000293abe0 000007fee94dfdd0    65560     
 49 ...     
 64 0000000002ac1378 000007fee94dfdd0    65560     
 65 0000000002ad1410 000007fee94dfdd0    65560     
 66...  
103 00000000165a71e0 000007fee94dfdd0 67108888     
104 0000000027c11000 000007fee94dfdd0 67108888     
105 total 102 objects
106 Statistics:
107               MT    Count    TotalSize Class Name
108 000007fee94dfdd0      102    138593344 System.Byte[]
109 Total 102 objects

果然,有那么多65592和65560啊 啊

随便找一个看一下:

0:005> !do 0000000002ba4790

复制代码
1 0:012> !do 0000000002ba4790 
2 Name: System.Byte[]
3 MethodTable: 000007fee94dfdd0
4 EEClass: 000007fee90e26b0
5 Size: 65590(0x10036) bytes
6 Array: Rank 1, Number of elements 65566, Type Byte
7 Element Type: System.Byte
8 Fields:
9 None

哦。这是个一维的数组,有65566字节,推测应该好像是short(int16)长度。

继续,

!gcroot 0000000002b42dd0

复制代码
0:012> !gcroot 0000000002b42dd0 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1d3c
RSP:18ef58:Root:00000000025c5b88(Ionic.Zip.ZipFile)->
00000000025d2578(Ionic.Zlib.ParallelDeflateOutputStream)->
00000000025dc528(System.Collections.Generic.List`1[[Ionic.Zlib.WorkItem, Ionic.Zip.Reduced]])->
000000000294ac38(System.Object[])->
0000000002b32d78(Ionic.Zlib.WorkItem)->
0000000002b42dd0(System.Byte[])
...
Scan Thread 10 OSTHread 3718

这里有点看头了! 看其跟对象 Ionic.Zip.ZipFile 这个对象占着没销毁的内存呢!

RSP:18ef58:Root :00000000025c5b88(Ionic.Zip.ZipFile)->

00000000025d2578(Ionic.Zlib.ParallelDeflateOutputStream)->

00000000025dc528(System.Collections.Generic.List`1\[Ionic.Zlib.WorkItem, Ionic.Zip.Reduced])->

000000000294ac38(System.Object\[\])->

0000000002b32d78(Ionic.Zlib.WorkItem)->

0000000002b42dd0(System.Byte\[\])

换一个看看:

复制代码
0:012> !gcroot 00000000029bc730 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 1d3c
RSP:18ef58:Root:00000000025c5b88(Ionic.Zip.ZipFile)->
00000000025d2578(Ionic.Zlib.ParallelDeflateOutputStream)->
00000000025dc528(System.Collections.Generic.List`1[[Ionic.Zlib.WorkItem, Ionic.Zip.Reduced]])->
000000000294ac38(System.Object[])->
00000000029ac6d8(Ionic.Zlib.WorkItem)->
00000000029bc730(System.Byte[])
...
Scan Thread 10 OSTHread 3718

查看下其代龄:

0:012> !gcgen 00000000029bc730

GEN 1

看到了,这个byte\[\]在1代。

到此为止,还记得有个静态字符串吧

private static string leakString;

我们回头再去看看,

!dumpheap -type String -min 1000

复制代码
0:012> !dumpheap -type String -min 1000
         Address               MT     Size
00000000025c26e0 000007fee94d7d90     8032     
00000000025cca30 000007fee94d7d90     1176     
00000000025cd308 000007fee94d7d90     1600     
000000001ae81000 000007fee94d7d90 134215704     
total 4 objects
Statistics:
              MT    Count    TotalSize Class Name
000007fee94d7d90        4    134226512 System.String
Total 4 objects

Next,

0:012> !do 00000000025c26e0

复制代码
0:012> !do 00000000025c26e0 
Name: System.String
MethodTable: 000007fee94d7d90
EEClass: 000007fee90de560
Size: 8026(0x1f5a) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: LEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKL....
EAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAK
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee94df000  4000096        8         System.Int32  1 instance             4001 m_arrayLength
000007fee94df000  4000097        c         System.Int32  1 instance             4000 m_stringLength
000007fee94d97d8  4000098       10          System.Char  1 instance               4c m_firstChar
000007fee94d7d90  4000099       20        System.String  0   shared           static Empty
                                 >> Domain:Value  000000000062b1d0:00000000025c1308 <<
000007fee94d9688  400009a       28        System.Char[]  0   shared           static WhitespaceChars
                                 >> Domain:Value  000000000062b1d0:00000000025c1a90 <<

再看下这个对象:

!dumpobj 00000000025c26e0

复制代码
0:012> !dumpobj 00000000025c26e0 
Name: System.String
MethodTable: 000007fee94d7d90
EEClass: 000007fee90de560
Size: 8026(0x1f5a) bytes
 (C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
复制代码
(C:\Windows\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: LEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKL....
EAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAKLEAK
Fields:
复制代码
MT Field Offset Type VT Attr Value Name 000007fee94df000 4000096 8 System.Int32 1 instance 4001 m_arrayLength 000007fee94df000 4000097 c System.Int32 1 instance 4000 m_stringLength 000007fee94d97d8 4000098 10 System.Char 1 instance 4c m_firstChar 000007fee94d7d90 4000099 20 System.String 0 shared static Empty >> Domain:Value 000000000062b1d0:00000000025c1308 << 000007fee94d9688 400009a 28 System.Char[] 0 shared static WhitespaceChars >> Domain:Value 000000000062b1d0:00000000025c1a90 <<

显示结果一样,String:LEAKLEAKLEAKLEAKLEAK......,字符串长度4000,和我们的测试代码吻合:

复制代码
1  public LeakTest()
2         {
3             for (int i = 0; i < 1000; i++)
4             {
5                 leakString += "LEAK";
6             }
7         }
相关推荐
wei1986211 天前
.net添加web引用和添加服务引用有什么区别?
java·前端·.net
pW3g3lLuu1 天前
.NET 高级开发 | http 接口对接和客户端开发技巧
网络协议·http·.net
CSharp精选营2 天前
.NET 8 与 .NET 9 支持终止倒计时:开发者需要了解什么
.net·lts·.net8·.net9·码农刚子·升级迁移·sts·支持终止
hez20105 天前
在 .NET 上构建超大托管数组
c#·.net·.net core·gc·clr
唐青枫11 天前
线程不是越多越快:C#.NET Thread 生命周期、同步与后台工作线程实战
c#·.net
唐青枫12 天前
别只会反射:C#.NET Emit 动态生成代码实战详解
c#·.net
Caco_D12 天前
一行代码抓遍全网 20 个热榜!Aneiang.Pa 4.0 发布 — 极简 .NET 爬虫库
爬虫·.net
咕白m62512 天前
.NET 环境下 Word 超链接批量提取方案
c#·.net