分享

追踪内存泄漏 | TechTarget IT专家网

 kittywei 2011-04-10
追踪内存泄漏
【5/8/2005 9:47:52】 【Rico Mariani】 【TechTarget】

  如果你认为你的内存发生了泄漏现象,或你正在思索到底是什么东西占用了堆栈,你可以跟着我的步骤来解决困扰你和你的朋友们许久的问题。这个过程很简单。

  这些步骤将会让你看到如何从发现一个可疑的内存泄漏现象到得出某个特殊的对象引用的过程,这个引用正维持着该对象处于存活状态。最后使用这些工具所得到的地址来查看资源的使用情况。

  步骤1:运行进程并将你所好奇的进程的状态浮现在你脑中确保选择一个能在你头脑中重现的情景,否则将不知道是否能在清理内存泄漏工作中取得进展。

  步骤2:使用tasklist命令来查找进程号

C:\>tasklist

Image Name                    PID Session Name     Session#    Mem Usage
========================= ====== ================ ======== ============
System Idle Process            0 RDP-Tcp#9               0         16 K
System                        4 RDP-Tcp#9                 0        112 K
smss.exe                    624 RDP-Tcp#9                0        252 K
 ...etc...
ShowFormComplex.exe        4496 RDP-Tcp#9     0     20,708 K
tasklist.exe                  3636 RDP-Tcp#9        0      4,180 K

  大家可以看到我的进程号#4496

  步骤3:使用VADump命令来得到进程的摘要

C:\>vadump -sop 4496
Category                   Total        Private  Shareable    Shared
                      Pages    KBytes    KBytes     KBytes    KBytes
 Page Table Pages      35       140       140       0         0
 Other System            15        60        60         0         0
 Code/StaticData       4596   18384    4020     3376   10988
 Heap                    215       860       860       0         0
 Stack                    30       120       120         0         0
 Teb                       4        16         16         0         0
 Mapped Data          129      516         0        24       492
 Other Data             157       628       624         4         0

 Total Modules          4596    18384    4020      3376    10988
 Total Dynamic Data  535      2140      1620        28       492
 Total System             50       200       200         0         0
Grand Total Working Set 5181  20724  5840      3404     11480

  大家可以看到最大代码量的进程(18384k)

  绝大多数CLR所使用的资源都处于”Other Data”下----这是因为GC Heap直接被VirtualAlloc进行分配 -- Other Data不会经过规则的窗口堆栈。同样这种所谓的”loader heaps”也不经过规则的窗口堆栈,loader heaps存放类型信息和被jit处理的代码。大部分传统的”Heap”分配都是从正运行的任何未被管理的进程中得到的。这就是带有控制管道的winform应用程序,因此就存在与这些东西相关的存储块。

  这儿并没有太多的”Other Data”,因此堆栈状态还是比较正常的。让我继续查看详细的CLR内存使用情况。

步骤4:使用windbg来装载SOS

C:\> windbg -p 4496

调试器使用这个命令来装载扩展DLL

0:004> .loadby sos mscorwks

  这告诉调试器在装载mscorwks.dll的地方对扩展”sos.dll”进行装载。那样可以保证你获得SOS的正确版本(这个文件应该与你正在使用的mscorwks文件相匹配)。

  步骤5:获取CLR内存信息

  这个命令用于显示我们所分配的内存信息。你所得到的输出信息可能和我的不一样,这主要取决于你所用的版本。但对一个简单的应用程序来说,你将会得到加载器堆栈的两个基本域结构(他们维持着以排序方式进行共享的对象)和存储器的第一个真实应用程序域(Domain 1)。当然还有被jit处理的代码。

0:004> !EEHeap
Loader Heap:
--------------------------------------
System Domain: 5e093770
...etc...
Total size: 0x8000(32768)bytes
--------------------------------------
Shared Domain: 5e093fa8
...etc...
Total size: 0xa000(40960)bytes
--------------------------------------
Domain 1: 14f0d0
...etc...
Total size: 0x18000(98304)bytes
--------------------------------------
Jit code heap:
LoaderCodeHeap: 02ef0000(10000:7000) Size: 0x7000(28672)bytes.
Total size: 0x7000(28672)bytes
--------------------------------------
Module Thunk heaps:
...etc...
Total size: 0x0(0)bytes
--------------------------------------
Module Lookup Table heaps:
...etc...
Total size: 0x0(0)bytes
--------------------------------------
Total LoaderHeap size: 0x31000(200704)bytes

  我们可以看到在内存被所加载的实体占用了200K,被jit处理的代码占用了28K.

  接下来是GC堆栈的输出信息。

=======================================
Number of GC Heaps: 1
generation 0 starts at 0x00a61018
generation 1 starts at 0x00a6100c
generation 2 starts at 0x00a61000
ephemeral segment allocation context: none
 segment    begin allocated     size
001b8630 7a8d0bbc  7a8f08d8 0x0001fd1c(130332)
001b4ac8 7b4f77e0  7b50dcc8 0x000164e8(91368)
00157690 02c10004  02c10010 0x0000000c(12)
00157610 5ba35728  5ba7c4a0 0x00046d78(290168)
00a60000 00a61000  00aac000 0x0004b000(307200)
Large object heap starts at 0x01a61000
 segment    begin allocated     size
01a60000 01a61000  01a66d90 0x00005d90(23952)
Total Size   0xcdd18(843032)
------------------------------
GC Heap Size   0xcdd18(843032)

  在你的屏幕上可能会有很多更小的堆栈片断,这是因为我是在内部调试构造上进行试验的,本来在转储时会出现很多象12字节的小片断。这样你就可以知道那些片断究竟是什么?他们到底有多大?还可以计算出当前内存的确切大小。

  从上图我们可以看到GC堆栈的大小为843K。而其他的数据共占了2M,其中CLR就占用了1M左右,剩下就是一些从winforms应用程序分配出来的位图
步骤6:转储GC堆栈统计数据

  接下来我们需要知道在这个具体实例的堆栈中到底有些什么类型

0:004> !DumpHeap -stat
... sorted from smallest to biggest ... etc. etc...

7b586c7c      436     10464 System.Internal.Gdi.WindowsGraphics
5ba867ac      208     11648 System.Reflection.RuntimeMethodInfo
7b586898      627     12540 System.Internal.Gdi.DeviceContext
5baa4954      677     39992 System.Object[]
5ba25c9c     8593    561496 System.String
Total 17427 objects

  注意,如果你不知道在你执行这条命令之前GC是否已经运行,那么以上数据中将可能会包括可达和不可达这两种对象。而且在这张表中你还可以看到一些已终止的对象。有时,在执行该命令之前强制运行GC来得到当前存活对象的内存信息是很有用的。也可以通过在运行GC前后将内存信息进行转储来判断哪些类别的对象已死亡。

  我们假设此时不应该为208 System.Reflection.RuntimeMethodInfo对象分配内存,即我们就可以认为这是一个内存泄漏。现在我们需要做的一件事就是使用CLR Profiler来查看这些对象是在哪里进行分配的 --所得到的信息将会充满半个屏幕。但我们可以在该调试器中得到一些其他的重要信息。

  步骤7:转储特殊类型的信息

  我们可以使用一个简单的命令来转储每一个包含给定字符类型的对象

0:004> !DumpHeap -type System.Reflection.RuntimeMethodInfo
 Address       MT     Size
00a63da4 5baa62c0       32    
00a63e04 5baa6174       20    
00a63e2c 5ba867ac       56    
00a63e64 5baa5fa8       16    
00a63e88 5baa5fa8       16    
00a63f24 5baa6174       20    
00a63f4c 5ba867ac       56    
00a63f84 5baa5fa8       16    
etc. etc. etc.
total 630 objects

Statistics:
      MT    Count TotalSize Class Name
5baa62c0        3        96 System.RuntimeType+RuntimeTypeCache+MemberInfoCache`1
       [[System.Reflection.RuntimeMethodInfo, mscorlib]]
5baa5fa8      211      3376 System.Reflection.CerArrayList`1
       [[System.Reflection.RuntimeMethodInfo, mscorlib]]
5baa6174      208      4160 System.Collections.Generic.List`1
       [[System.Reflection.RuntimeMethodInfo, mscorlib]]
5ba867ac      208     11648 System.Reflection.RuntimeMethodInfo
Total 630 objects

  注意,我们所需要的类型是System.Reflection.RuntimeMethodInfo,显示的内容中含有一个方法表,其中包括5ba867ac,它所对应的都是一些56字节的对象。现在我们就可以来研究他们,看看是什么让他们保持存活状态。
步骤8:确定可疑泄漏的根源

  转储中有一行是

00a63e2c 5ba867ac       56

因此我们可以知道我们要找的对象所在的地址是00a63e2c。接下来看是什么让他们保持存活状态。

0:004> !gcroot 00a63e2c
Scan Thread 0 OSTHread 1598
Scan Thread 2 OSTHread 103c

DOMAIN(0014F0D0):
HANDLE(WeakLn):3f10f0:
Root:00a63d20(System.RuntimeType+RuntimeTypeCache)
->00a63da4(System.RuntimeType+RuntimeTypeCache+MemberInfoCache`1
  [[System.Reflection.RuntimeMethodInfo,mscorlib]])
->00a63e88(System.Reflection.CerArrayList`1
  [[System.Reflection.RuntimeMethodInfo, mscorlib]])
->00a63e98(System.Object[])
->00a63e2c(System.Reflection.RuntimeMethodInfo)

DOMAIN(0014F0D0):
HANDLE(Pinned):3f13ec:
Root:01a64b50(System.Object[])
->00a62f20(System.ComponentModel.WeakEventHandlerList)
->00a63fb4(System.ComponentModel.WeakEventHandlerList+ListEntry)
->00a63ec4(System.ComponentModel.WeakEventHandlerList+ListEntry)
->00aa5f6c(System.ComponentModel.WeakDelegateHolder)
->00a63e2c(System.Reflection.RuntimeMethodInfo)

  在以上内容中我加入了一些附加的行以方便我们阅读。

  Gcroot命令告诉你该对象是否可达,如果可达则告诉你如何从每个root到达该对象。这种转储将不会包含所有到达该对象的路径,但至少包含一条可以找到该对象的路径 – 通常这已经足够了。如果转储中出现了多条路径,那么这些路径都含有相同的尾部。如果该对象可达(说明该对象可能只有弱引用存在,因此在下一次查看时该对象就不会再出现了),你就应该从剩下的引用中找到相关的线索。从那些引用中你就可以决定哪些指针需要指向空来释放对象所占用的内存。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多