[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory

原文来自:http://blogs.msdn.com/b/tess/archive/2008/02/20/net-debugging-demos-lab-3-memory-review.aspx

1. 打开Performance Monitor。在Counter Logs里添加New Log Settings,命名为Lab3-Mem。添加的Log对象为.NET CLR Memory和Process。

2. 使用TinyGet执行命令个 tinyget –srv:localhost –uri:/buggybits/links.aspx –loop:4000

3. 命令执行以后,可以看到w3wp.exe进程的大小持续增长,并且在tinyeget命令结束以后,程序所站大小仍然不减:

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第1张图片

4. 等待一段时间以后,在Performance monitor里面打开刚才存储的日志,添加Process对象的Virtual bytes和Private Bytes计数器,添加.NET CLR Memory对象的Bytes in all Heaps计数器,将他们的scale调整为0.00000001:

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第2张图片

可见随着Bytes in All heaps(紫红)的增长,进程的virtual bytes(白色)和Private Bytes(青色)也在增长。因此可以判定是.NET进程的heap的增长带来了内存消耗的增长,并且内存增长之后没有回收。

5.  切换到WinDbg目录在命令行执行 adplus –hang –pn w3wp.exe –quiet 注意此时应该将应用程序池的各种Recycle选项全部关闭。否则可能因为抓取时间太长,w3wp.exe进程关闭,hang dump抓取失败。

6. 使用WinDbg打开刚才得到的dump文件。执行 .loadby sos.dll mscorwks

7. 执行 !eeheap -gc查看GC的情况,可见有一个heap,因为是单核。如果是双核,则会有两个heap。Heap大小为700多兆,可见是heap出问题了。

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第3张图片

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第4张图片

8. 执行 !dumpheap –stat:

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第5张图片

可见System.String占据了大部分的内存空间。

9. 执行 !dumpheap –type Link并终止输出。找到其中一个object的地址02eab13c,执行 !do 02eab13c:可以发现Stringbuilder和System.String都是包含在Link类型中的类型。

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第6张图片

这就解释了!dumpheap –stat中看到的占据heap最多的对象的类型的内存占用情况。

10. 执行 !dumpheap –type System.String 并强行终止输出。以其中一个对象的地址 027da238为例,执行 !do 027da238,可以找到这个string对象存储的值。

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第7张图片

11. 为了找到为什么该对象没有被回收,查看哪些线程对它还有root,因此执行命令 !gcroot 027da238:

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第8张图片

可见Finalizer queue对其有引用,因此可以推断Finalizer queue还没有对这个对象执行Finalize方法。

12. 执行 !threads找到Finalize queue的线程是 17:

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第9张图片

13. 执行 ~17s切换到该线程。执行 !clrstack:

image

可见这就是Finalize queue上对该对象的root一直保持着的原因:线程休眠了。

14. 在App_Code文件夹里找到哦啊Link.cs:

[ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory_第10张图片

可见该对象规定了Finalize方法,因此会被放到Finalize queue上去。而这个方法里面,导致执行该方法的Finalize线程休眠了,所以对象一直有引用,内存得不到释放。

20110218添加:

一、本例被判定为High Memory的问题。这样的问题有三种情况,一是Manged Memory泄漏,二是Native Memory泄漏,第三是Load Library出问题。对于这三种情况,可以通过Performance Monitor来进行基本判定。其中.NET CLR Memory object的#bytes in all heaps 这个counter,Process Object的Virutual Bytes和Private bytes这两个counter。

  1. 如果是Managed Memory泄漏,那么bytes in all heaps,private bytes和virtual bytes会同市上升。

  2. 如果是Native Memory泄漏,那么bytes in all heaps不会上升,但是private bytes和virtual bytes都会上升。

  3. 如果是Load Library的问题,那么bytes in all heaps和private bytes都不会上升,但是virutal bytes会上升。

二、本例debug时首先执行了!address -summary 命令,得到如下结果:


0:000> !address -summary
-------------------- Usage SUMMARY --------------------------
    TotSize (      KB)   Pct(Tots) Pct(Busy)   Usage
   373b7000 (  904924) : 21.58%    85.85%    : RegionUsageIsVAD
   bfa89000 ( 3140132) : 74.87%    00.00%    : RegionUsageFree
    76e6000 (  121752) : 02.90%    11.55%    : RegionUsageImage
     67c000 (    6640) : 00.16%    00.63%    : RegionUsageStack
          0 (       0) : 00.00%    00.00%    : RegionUsageTeb
    144a000 (   20776) : 00.50%    01.97%    : RegionUsageHeap
          0 (       0) : 00.00%    00.00%    : RegionUsagePageHeap
       1000 (       4) : 00.00%    00.00%    : RegionUsagePeb
       1000 (       4) : 00.00%    00.00%    : RegionUsageProcessParametrs
       2000 (       8) : 00.00%    00.00%    : RegionUsageEnvironmentBlock
       Tot: ffff0000 (4194240 KB) Busy: 40567000 (1054108 KB)

-------------------- Type SUMMARY --------------------------
    TotSize (      KB)   Pct(Tots)  Usage
   bfa89000 ( 3140132) : 74.87%   :834e000 (  134456) : 03.21%   : MEM_IMAGE
     95a000 (    9576) : 00.23%   : MEM_MAPPED
   378bf000 (  910076) : 21.70%   : MEM_PRIVATE

-------------------- State SUMMARY --------------------------
    TotSize (      KB)   Pct(Tots)  Usage
   34bea000 (  864168) : 20.60%   : MEM_COMMIT
   bfa89000 ( 3140132) : 74.87%   : MEM_FREE
    b97d000 (  189940) : 04.53%   : MEM_RESERVE

Largest free region: Base 80010000 - Size 7fefa000 (2096104 KB)

从这里面可以得到的信息由:

  1. RegionUsageIsVAD占有最多的内存份额,它与GCHeap相关。

  2. Pct(Tots)指的是该部分memory占总共的virtual memory的比例,而Pct(Busy)指的是该部分memory占Private memory的比例。

三、由于已经能够判定是Managed Memory有问题,我们需要进一步查看是Managed Memory的哪一步有问题,因此执行!eeheap -gc 对GC占有的memory进行enumerate:


0:000> !eeheap -gc Number of GC Heaps: 2 ------------------------------
Heap 0 (001aa148)
generation 0 starts at 0x32f0639c
generation 1 starts at 0x32ae3754
generation 2 starts at 0x02eb0038
ephemeral segment allocation context: none
 segment    begin allocated     size
001bfe10 7a733370  7a754b98 0x00021828(137256)
001b0f10 790d8620  790f7d8c 0x0001f76c(128876)
02eb0000 02eb0038  06eacb28 0x03ffcaf0(67095280)
100b0000 100b0038  140a4a08 0x03ff49d0(67062224)
18180000 18180038  1c15c650 0x03fdc618(66962968)
20310000 20310038  242eb99c 0x03fdb964(66959716)
28310000 28310038  2c2f04cc 0x03fe0494(66978964)
31190000 31190038  33185ff4 0x01ff5fbc(33513404)
Large object heap starts at 0x0aeb0038
 segment    begin allocated     size
0aeb0000 0aeb0038  0aec0b28 0x00010af0(68336)
Heap Size  0x15fd1310(368907024)
------------------------------
Heap 1 (001ab108)
generation 0 starts at 0x36e665bc
generation 1 starts at 0x36a28044
generation 2 starts at 0x06eb0038
ephemeral segment allocation context: none
 segment    begin allocated     size
06eb0000 06eb0038  0aea58d4 0x03ff589c(67066012)
14180000 14180038  1817eda8 0x03ffed70(67104112)
1c310000 1c310038  202f0550 0x03fe0518(66979096)
24310000 24310038  28304ca8 0x03ff4c70(67062896)
2c310000 2c310038  2fd62a48 0x03a52a10(61155856)
35190000 35190038  372253f4 0x020953bc(34165692)
Large object heap starts at 0x0ceb0038
 segment    begin allocated     size
0ceb0000 0ceb0038  0ceb0048 0x00000010(16)
Heap Size  0x15ab1570(363533680)
------------------------------ GC Heap Size  0x2ba82880(732440704)
 
 

 四、执行!dumpheap -stat查看heap中各种类型占用的内存的大小。这里原文重点解释了Size的意义,用!dumpheap -stat 得到的类型的size值得话并不真实,因为有的对象只是包含了一个别的对象的引用而已,因而计算大小的话只会计算指针大小。需要使用!objsize才能计算出真正的大小。

五、由于对象没有被回收,是因为他们的引用没有得到释放。因此执行!gcroot <address of an object>查看可以的对象的引用情况。

六、在得知对象没有被引用的原因是对象正在被Finalize的过程中时,可以执行!FinalizeQueue查看这个queue引用了哪些对象。这里原文强调了为什么对象会被FinalizeQueue引用:只有默认具有析构函数的系统类型或者显式定义了析构函数的类型的对象,才会在Garbage Collect的时候,被Finalize Queue引用执行其Destructor的方法。

你可能感兴趣的:([ASP.NET Debugging BuggyBits读书笔记] Lab03 Memory)