在2010年的silverlight开发中项目组遇到了一些内存过大问题,经过同事们共同努力总算解决了,下面分享我们用WinDBG工具调试的一些经验。下面我们以WinFrom为例(在silverlight,和ASP.NET中基本雷同)。
首先我们创建一个简单的Winfrom项目,MainFrom为主窗体,Form1和Form2为两个窗体,Form1使用了UserControl1控件,Form2使用了UsrControl2控件。如下图
我明年将工程编译好,在bin\Debug目录下启用应用程序,并且启动WinDBG界面如下,将进程Attach进来。
让您进程继续运行。Windbg附加到进程后会将指定的进程阻塞(类似于IDE中的调试阻塞状态),通过 Go 菜单命令或者输入g命令 可让进程继续运行。通过Break菜单可让进程阻塞,阻塞后我们就可以通过命令调试。
加载SOS调试扩展包
.load C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll (如果是silverlight则是.loadby sos coreclr,Silvrlight的核心组件为coreclr).
下面我们将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]]
通过 type 参数查看内存中指定类型的对象 !dumpheap –type WinDBG,支持模糊查询,下面我们就是查询全名包含WinDBG下的所有未回收的对象。
我们可以看到名称包含WinDBG的有一个MainFrom未回收,两个UserControl1未回收。
分析:MainFrom未关闭,没有回收是正常现象,UserControl1两个未回收就不正常了。
下面我们需要找出UserControl1为什么没有回收掉了。我们知道对象如果不能被GC回收的情况有:1.引用了Com组件;2.有依赖某个其它的对象,这个对象不能回收;3.等。现在我们就猜测为以上两种情况。
开始查找原因:
1.首先我们找到UserControl1的内存地址,用-mt参数dump出内存地址 !dumpheap -mt 002b7efc。002b7efc为UserControl1的类型地址。
2.我们可以看到两个UserControl1的地址分别为01bc9018 和01bcde5c .
我们用gcroot命令查看对象的引用关系,gcroot的完整参数是这样的!GCRoot [-nostacks] <Object address>
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 选项按照DumpObj和DumpVC格式显示元素的细节。 -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 | 反汇编 |
! | 所有扩展命令的起始符 |
. | 所有元命令的起始符号 |
更多命令可参考张银奎的《软件调试》,新年刚过有些潦草,望园友见谅。