新年+情人节礼物,WinDBG找出你内存溢出的地方

      2010年的silverlight开发中项目组遇到了一些内存过大问题,经过同事们共同努力总算解决了,下面分享我们用WinDBG工具调试的一些经验。下面我们以WinFrom为例(在silverlight,和ASP.NET中基本雷同)。

     首先我们创建一个简单的Winfrom项目,MainFrom为主窗体,Form1和Form2为两个窗体,Form1使用了UserControl1控件,Form2使用了UsrControl2控件。如下图    

     image

      我明年将工程编译好,在bin\Debug目录下启用应用程序,并且启动WinDBG界面如下,将进程Attach进来。

image

image

     让您进程继续运行。Windbg附加到进程后会将指定的进程阻塞(类似于IDE中的调试阻塞状态),通过 Go 菜单命令或者输入g命令image 可让进程继续运行。通过Break菜单可让进程阻塞,阻塞后我们就可以通过命令调试。

  加载SOS调试扩展包

      .load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll (如果是silverlight则是.loadby sos coreclr,Silvrlight的核心组件为coreclr).

      image

    下面我们将Form1和Form2窗体都打开两个个然后关闭,接着手动调用一次GC回收(因为本次我们主要看GC不能回收的对象)。

    查看内存中所有对象

          !dumpheap –stat

          !dumpheap的参数规格为

       !dumpHeap [-stat]
          [-strings]
          [-short]
          [-min <size>]
          [-max <size>]
          [-thinlock]
          [-startAtLowerBound]
          [-mt <MethodTable address>]
          [-type <partial type name>]
          [start [end]]

image

通过 type 参数查看内存中指定类型的对象 !dumpheap –type WinDBG,支持模糊查询,下面我们就是查询全名包含WinDBG下的所有未回收的对象。

image

我们可以看到名称包含WinDBG的有一个MainFrom未回收,两个UserControl1未回收。

分析:MainFrom未关闭,没有回收是正常现象,UserControl1两个未回收就不正常了。

下面我们需要找出UserControl1为什么没有回收掉了。我们知道对象如果不能被GC回收的情况有:1.引用了Com组件;2.有依赖某个其它的对象,这个对象不能回收;3.等。现在我们就猜测为以上两种情况。

开始查找原因:

    1.首先我们找到UserControl1的内存地址,用-mt参数dump出内存地址 !dumpheap -mt 002b7efc。002b7efc为UserControl1的类型地址。

image 2.我们可以看到两个UserControl1的地址分别为01bc9018 和01bcde5c .

   我们用gcroot命令查看对象的引用关系,gcroot的完整参数是这样的!GCRoot [-nostacks] <Object address>

image image 为重点怀疑对象,下面排查代码。

public UserControl1()
{
    InitializeComponent();
    Application.ApplicationExit += new EventHandler(OnApplicationExit);
}

private  void OnApplicationExit(object sender, EventArgs e)
{
}

Application事件为应用级别,我们可以确定是因为它引起对象不能回收,因为Application的只有在程序退出时候才会销毁。

附:

命令 描述

BPMD [<module name> <method name>] [-md <MethodDesc>]

建立一个断点在指定模块的指定方法上。

如果指定模块和方法尚未被载入,该命令等到该模块被载入并且被即时(just-in-time)编译的通知后再建立断点。

CLRStack [-a] [-l] [-p]

只提供托管代码的栈跟踪。

-p 选项显示托管函数的参数。

-l 选项显示在一个框架里局部变量的信息。SOS调试扩展无法检索局部变量的名字,所以局部变量的输出格式为<local address> = <value>。

-a (all) 选项是-l-p组合的快捷方式。

在x64和基于IA-64的平台上,SOS调试扩展不显示过渡框架(Transition Frames)。

COMState

列出每个线程COM单元模型和可用的上下文指针。

DumpArray [-start <startIndex>] [-length <length>] [-details] [-nofields] <array object address>

-或者-

DA [-start <startIndex>] [-length <length>] [-detail] [-nofields] <array object address>

检查一个数组对象的元素。

-start 选项指定显示元素的起始索引号。

-length 选项指定要显示的元素数目。

-detail 选项按照DumpObjDumpVC格式显示元素的细节。

-nofields 选项使数组显示不包括字段。仅当指定 -detail 选项时该选项才可用。

DumpAssembly <Assembly address>

显示一个汇编集的有关信息。

如果存在多个模块,DumpAssembly命令将它们全部列出。

你可以用DumpDomain命令得到汇编集地址。

lm

显示已加载模块

DumpDomain [<Domain address>]

枚举在指定AppDomain对象地址里面装载的每一个Assembly对象。当不带参数调用DumpDomain命令时,它列出一个进程中所有的AppDomain对象。

DumpHeap [-stat] [-min <size>][-max <size>] [-thinlock] [-mt <MethodTable address>] [-type <partial type name>][start [end]]

显示关于垃圾收集堆的信息和有关对象的收集统计。

DumpHeap命令如果在垃圾收集器堆中检测到过多的碎片,它显示一个警告。

-stat 选项限制输出内容只有统计的类型摘要。

-min 选项忽略那些尺寸小于size参数的对象,以字节为单位。

-max 选项忽略那些尺寸大于size参数的对象,以字节为单位。

-thinlock 选项报告ThinLocks。更多信息请看SyncBlk命令。

-mt 选项只列出符合所指定MethodTable结构的那些对象。

-type 选项只列出类型名字子串匹配指定字符串的那些对象。

参数 start 指定开始列出的地址。

参数 end 指定停止列出的地址 。

U 反汇编
所有扩展命令的起始符
. 所有元命令的起始符号

更多命令可参考张银奎的《软件调试》,新年刚过有些潦草,望园友见谅。

代码下载点击此处

posted on 2011-02-12 12:18  Mr.Wrong居然被人用了  阅读(5764)  评论(16编辑  收藏  举报

导航