WinDbg初始体验2,抓住不放的恶棍(OutOfMemory案例分析Ⅰ)

上篇中,介绍了如何使用adplus来捕捉dump。我们可以使用这个命令:adplus -Crash -p 进程ID(或-IIS) -quiet -fullonfirst -o C:\dumps 。但是我们在哪里执行这段命令呢?在控制台命令行,切换到Windbg的安装目录下。然后执行上面的命令,当你监控的进程出现异常时,它就会捕捉到该进程的所有相关信息到dump文件中(前提是fullonfirst)。比如你的进程使用内存是1G,那么dump文件也会在1G左右。此时如果你想捕捉OutOfMemory的dump,不一定要等到这个异常出现后。你可以在内存到一定量的时候,去获取dump文件,基本的信息是一样的。

我捕捉到的dump文件是1.31 GB ,太大了,过了这个值,网站马上就Crash掉了。可能是由于服务器物理架构的原因,这个dump文件在我本机打不开,只有在服务器上调试了。打开dump文件,先来一段看不懂的提示:

This dump file has an exception of interest stored in it.
The stored exception information can be accessed via .ecxr.
(39c.13c): CLR exception - code e0434f4d (first/second chance not available)
eax=0275f290 ebx=000d6848 ecx=00000000 edx=00000024 esi=0275f31c edi=e0434f4d
eip=7c80bee7 esp=0275f28c ebp=0275f2e0 iopl=0         nv up ei pl nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200202
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for kernel32.dll -
kernel32!RaiseException+0x3c:
7c80bee7 5e              pop     esi

大概意思就是说:dump文件中有一个异常,各个寄存器的地址(应该是吧),找不到kernel32.dll的Symbol ,异常是由它引发,略略。

这时,我们在工作窗体中执行命令加载SOS.dll。.load C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\SOS.dll

接下来我们就可以开始工作了,先执行命令:!eeheap -gc 看看托管堆上都放着些什么。

0:012> !eeheap -gc
PDB symbol for mscorwks.dll not loaded
Number of GC Heaps: 4
------------------------------
Heap 0 (000e28e0)
generation 0 starts at 0x6d110044
generation 1 starts at 0x6d110038
generation 2 starts at 0x02860038
ephemeral segment allocation context: none
 segment    begin allocated     size
1bd8c710 7b45382c  7b46a268 0x00016a3c(92732)
000f6b40 7a72c42c  7a74d308 0x00020edc(134876)
000eafb0 790d5588  790f4b38 0x0001f5b0(128432)
02860000 02860038  0685ea18 0x03ffe9e0(67103200)
2cca0000 2cca0038  30c9db38 0x03ffdb00(67099392)
44f30000 44f30038  48d01460 0x03dd1428(64820264)
6d110000 6d110038  6d112050 0x00002018(8216)
Large object heap starts at 0x12860038
 segment    begin allocated     size
12860000 12860038  14844b50 0x01fe4b18(33442584)
35c80000 35c80038  37b42920 0x01ec28e8(32254184)
Heap Size  0xfccdbe8(265083880)
------------------------------
Heap 1 (000e39b0)
generation 0 starts at 0x4ff2e1d4
generation 1 starts at 0x4ff2b4fc
generation 2 starts at 0x06860038
ephemeral segment allocation context: none
 segment    begin allocated     size
06860000 06860038  0a85db18 0x03ffdae0(67099360)
20aa0000 20aa0038  24a9ccb0 0x03ffcc78(67095672)
37d90000 37d90038  3bd8d500 0x03ffd4c8(67097800)
4efc0000 4efc0038  4ff321e0 0x00f721a8(16196008)
Large object heap starts at 0x14860038
 segment    begin allocated     size
14860000 14860038  16846380 0x01fe6348(33448776)
30d60000 30d60038  32d0e808 0x01fae7d0(33220560)
52fc0000 52fc0038  535b4598 0x005f4560(6243680)
Heap Size  0x114f2e40(290401856)
------------------------------
Heap 2 (000e4e70)
generation 0 starts at 0x447270a0
generation 1 starts at 0x447268f8
generation 2 starts at 0x0a860038
ephemeral segment allocation context: none
 segment    begin allocated     size
0a860000 0a860038  0e85f1e4 0x03fff1ac(67105196)
24ca0000 24ca0038  28c9f548 0x03fff510(67106064)
40730000 40730038  4472b0ac 0x03ffb074(67088500)
Large object heap starts at 0x16860038
 segment    begin allocated     size
16860000 16860038  188296c8 0x01fc9690(33330832)
1e890000 1e890038  20849ab0 0x01fb9a78(33266296)
4cfc0000 4cfc0038  4ef6aef0 0x01faaeb8(33205944)
Heap Size  0x11f276f0(301102832)
------------------------------
Heap 3 (000e63a8)
generation 0 starts at 0x40694068
generation 1 starts at 0x4069405c
generation 2 starts at 0x0e860038
ephemeral segment allocation context: none
 segment    begin allocated     size
0e860000 0e860038  1285df98 0x03ffdf60(67100512)
28ca0000 28ca0038  2cc9ef68 0x03ffef30(67104560)
3c730000 3c730038  4069a074 0x03f6a03c(66494524)
Large object heap starts at 0x18860038
 segment    begin allocated     size
18860000 18860038  1a85c5e0 0x01ffc5a8(33539496)
33c80000 33c80038  35c3b740 0x01fbb708(33273608)
56bb0000 56bb0038  57133f60 0x00583f28(5783336)
Heap Size  0x104a2aa4(273296036)
------------------------------
GC Heap Size  0x4358abbc(1129884604)

首先提示我们一下,存放mscorwks.dll symbol 的PDB文件不存在。然后告诉我们有4个GC拖管堆的存在。每个拖管堆都详细罗列出了每一个代龄堆的起始地址,以及每个堆上面存放的对象所占用内存空间。最后给我们统计了一下GC堆的大小,共有1129884604 bytes (1.1G左右),也就是说,当进程的内存大部分都被拖管堆给占用了。大对象有不少呢!接下来的命令是!dumpheap -min 85000 -stat 统计一下大对象的基本信息。

0:012> !dumpheap -min 85000 -stat
------------------------------
Heap 0
total 213 objects
------------------------------
Heap 1
total 207 objects
------------------------------
Heap 2
total 296 objects
------------------------------
Heap 3
total 230 objects
------------------------------
total 946 objects
Statistics:
      MT    Count    TotalSize Class Name
7912254c        6       746928 System.Object[]
7912677c       13      5889000 System.Single[]
79122610        3      9465840 System.Collections.Hashtable+bucket[]
000d88f0      105     36952120      Free
7912273c      335     37943440 System.Byte[]
79122414      484    219252000 System.Int32[]
Total 946 objects

好家伙,有946个大对象。而且单单整型数组就占用了200多M的内存,是什么东西在搞鬼呢?看看下面命令,这会是列出所有的大对象,不是得到统计信息:!dumpheap -min 85000

Heap 0
 Address       MT     Size
128823a0 7912273c   113264    
128a2818 7912273c   113264    
128be298 79122414   453000    
1292cc20 7912273c   113264    
12948690 000d88f0   107448 Free
12962c68 7912254c   124488    
129812b0 000d88f0   107296 Free
1299b5d0 79122414   453000    

.......

35ae0ed8 000d88f0   286800 Free
35b26f28 7912273c   113264    
35b42998 7912677c   453000    
35bb1338 79122414   453000    
35c1fcd0 7912273c   113264    
56bb0038 79122414   453000    
56c1e9c0 7912273c   113264    
56c3a430 000d88f0   339776 Free
56c8d370 79122414   453000    
56cfbd08 7912273c   113264    
56d17788 79122414   453000    
56d86110 79122414   453000   

total 230 objects
------------------------------
total 946 objects
Statistics:
      MT    Count    TotalSize Class Name
7912254c        6       746928 System.Object[]
7912677c       13      5889000 System.Single[]
79122610        3      9465840 System.Collections.Hashtable+bucket[]
000d88f0      105     36952120      Free
7912273c      335     37943440 System.Byte[]
79122414      484    219252000 System.Int32[]   
看出规律了吧?从Heap0到Heap3,都是存在这样的两种大小的对象:113264  和 453000  。有规律就好办事了,找一个出来看看,执行命令:!gcroot 0x34e9bee0

0:012> !gcroot 0x34e9bee0
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 12 OSTHread 13c
Scan Thread 18 OSTHread 1580
Scan Thread 19 OSTHread 1510
.....扫描太多线程了
DOMAIN(000FF798):HANDLE(Pinned):1aec11ec:Root:12870d18(System.Object[])->
070db534(Lucene.Net.Search.FieldSortedHitQueue+AnonymousClassCache)->
070db540(System.Collections.Hashtable)->
0634bfe4(System.Collections.Hashtable+bucket[])->
3e886dd4(System.Collections.Hashtable)->
3e886e0c(System.Collections.Hashtable+bucket[])->
3e887164(Lucene.Net.Search.FieldSortedHitQueue+AnonymousClassScoreDocComparator2)->
3e887154(Lucene.Net.Search.StringIndex)->
34e9bee0(System.Int32[])

看看,这都是什么东西啊?在程序中使用了Lucene.net,赶紧打开Reflector。查了查,看了看。我看到了缓存对象的实现,看到了每搜索一次都往缓存里加对象。但是我却没有看到缓存的过期和回收。怎么办呢?没有开关,没有配置。我只有通过源码找到Cache的实现,将它的public virtual object Get(IndexReader reader, object key)方法重写了,我不缓存了,不行吗!然后向Lucene.net报告这个情况。(也不知道这该不该认为是一个BUG呢,顺便说一下,Lucene.net已经有 2.1.0.2版本了,只不过是beta,还没有正式公布出来,我是在SVN服务器上看到的)。

缓存,是大多数内存问题的根源所在。有很多简单的缓存实现都是用一个Hashtable来缓存对象,但是它们却始终都没有做缓存过期的回收工作,甚至连过期策略都没有。这由不得让我想起了Enterprise Library的缓存,它有相当完善的缓存过期管理。但由于entlib使用和配置的问题,现在已基本不用了。现用的缓存基本也都使用HttpRumtime.Cache的缓存,虽然听很多人说使用它会有很多问题,比如自动释放。但是使用它也有很多好处呀,比如在web.config我们可以配置它的缓存策略,配置它要不定期回收,配置它可以使用的内存比例。总之,在一些场合,我们还是可以使用的。前提是,它适合我们,不会给你的程序带来很多的负面影响。使用Hashtable来缓存也是一样的,只要缓存项是有限的,那就可以用。

总算是找到了一个成功的找到了一个问题,兴奋之余接下来就是升级,看看效果了。但是,这个故事结束之后,看到的服务器的内存利用率还是暴涨,似乎没有任何改观?难道是我们的修改没有正确?预知详情,还请继续关注下一篇:《WinDbg初始体验3,剪不断的链条

下一篇引用:WinDbg初始体验3,剪不断的链条

上一篇引用:WinDbg初始体验,问题来了怎么办?

阿不

你可能感兴趣的:(OutOfMemory)