遇到了问题:
当你在使用ASP.NET开发应用程序是碰到了OutOfMemoryExceptions.
起因:
参看下文分析。。。
解决方案:
使用Windbg查看托管堆。
是否是内存泄漏?
首先使用Perfmon工具来查看内存程序的内存使用情况。如果内存在缓慢的增长并且一直都没有被释放,那就是内存泄漏。如果内存像过山车一样的突然拔地而起又突然回落到正常水平,那说明你的程序中使用了巨消耗内存的对象或操作,但是最终还是被GC给回收了。
如何排查?
下面我们来一步一步试验如何排查类似的情况。
1. 获取内存的转储(Memory dump)
这一步可以使用Windbg或者Adplus来完成。如果你没有安装Windbg, 建议你先查看Johan Steve的文章How to install Windbg and get your first memory dump
.
2. 打开转储文件,加载SOS
使用Windbg打开dump文件,并且加载SOS扩展。 如果你的程序使用的是.net framework 2.0,看看文件夹"C:/Windows/Microsoft.NET/Framework/v2.0.50727"是否存在. 然后输入:
.load [path]/SOS
来加载SOS。
注:.net Framework 4中可以使用
.loadby sos clr
这个命令。
3. 运行dumpheap命令
执行这个命令:!dumpheap –stat
这个命令会统计托管堆上的所有对象,并且以容易看明白的排版形式显示出来。内容包含:
1. 对象的方法表。
2. 对象类型在托管堆上包含其他对象的数量。
3. 以byte为单位所列出对象的总的大小。
4. 对象的类型名。
当心不要忘了参数-stat。如果忘了的话Windbg将会在你的屏幕上密密麻麻的打印出托管堆中每一个对象的地址,那些数据绝大多数都是我们不需要的。
下面我们来看看!dumpheap打印出来的信息有何玄机
来看一个例子:
1:
0:000> !dumpheap -stat
2:
Statistics:
3:
MT Count TotalSize Class Name
4:
7a787cc4 1 12 System.IO.FileSystemWatcher+FSWAsyncResult
5:
7a75904c 1 12 System.Diagnostics.OrdinalCaseInsensitiveComparer
6:
7a7575cc 1 12 System.CodeDom.Compiler.CodeDomConfigurationHandler
7:
7a7571a8 1 12 System.Net.DefaultCertPolicy
8:
7a75661c 1 12 System.Diagnostics.TraceListenerCollection
9:
7a755834 1 12 System.Diagnostics.PerformanceCounterCategoryType
10:
........CONTINUED.........
11:
68a66a88 227,559 12,743,304 System.Web.UI.WebControls.Literal
12:
68a2f7fc 399,272 14,373,792 System.Web.UI.ControlCollection
13:
68a92e2c 768,731 33,824,164 System.Web.UI.Control+OccasionalFields
14:
68a884a0 641,952 38,517,120 System.Web.UI.LiteralControl
15:
79124228 879,515 43,394,976 System.Object[]
16:
790fa3e0 1,431,594 122,806,484 System.String
17:
18:
Total 10,389,625 objects, Total size: 463,313,540
在这个dump中我有1,431,594个字符串对象,总共占据了122M内存;还有879,515个对象,总共占据了43M内存。
托管堆中的对象很有可能比看起来的要大的多
“总字节数”这一栏的数据不总是100%的正确。来看一下LiteralControls的“总字节数”为例。看起来它们只占用了38M多的数据量,但事实上呢?“总字节数”只是对对象结构的大小做了统计,但是一些变量如字符串,整型和子对象并没有包括在内。说明白些,让我们反过来想如果不是这样做的话,“总字节数”将是一个冗余而且相当大的值。举个例子,LiteralControl这个对象包含有为数众多的子对象,其中有三个是字符串。而字符串事实上它们的大小将会被统计在System.String对象中。
使用!dumpobj
我们来仔细看一下System.Web.UI.LiteralControls其中一个对象。我们使用
1:
!dumpheap -type System.Web.UI.LiteralControl
来列出所有的System.Web.UI.LiteralControls对象。
1:
0:000> !dumpheap -type System.Web.UI.LiteralControl
2:
Address MT Size Gen
3:
023ea0a8 68a884a0 60 2 System.Web.UI.LiteralControl
4:
023ea0e4 68a884a0 60 2 System.Web.UI.LiteralControl
5:
023ea374 68a884a0 60 2 System.Web.UI.LiteralControl
6:
023ea460 68a884a0 60 2 System.Web.UI.LiteralControl
7:
023ea510 68a884a0 60 2 System.Web.UI.LiteralControl
8:
023eab3c 68a884a0 60 2 System.Web.UI.LiteralControl
9:
........CONTINUED........
10:
023fe31c 68a884a0 60 2 System.Web.UI.LiteralControl
11:
023fe414 68a884a0 60 2 System.Web.UI.LiteralControl
12:
023fe4c4 68a884a0 60 2 System.Web.UI.LiteralControl
13:
023fe500 68a884a0 60 2 System.Web.UI.LiteralControl
在“Size”一栏中我们看到每一个LiteralControl占了60字节大小。刚才说了这是这总对象的在内存结构中的大小,而不是包含引用的子对象和属性。下面我们挑一个对象来使用!dumpobj(do是它的简化形式)命令来做进一步的分析。给出的结果如下:
1: 0:000> !do 023ea0a8
2:
Name: System.Web.UI.LiteralControl
3:
MethodTable: 68a884a0
4:
EEClass: 68a88428
5:
Size: 60(0x3c) bytes
6:
GC Generation: 2
7:
(C:/WINDOWS/assembly/GAC_32/System.Web/2.0.0.0__b03f5f7f11d50a3a/System.Web.dll)
8:
Fields:
9:
MT Field Offset Type VT Attr Value Name
10:
790fa3e0 4001fe0 4 System.String 0 instance 00000000 _id
11:
790fa3e0 4001fe1 8 System.String 0 instance 00000000 _cachedUniqueID
12:
68a2af44 4001fe2 c ...em.Web.UI.Control 0 instance 023e8864 _parent
13:
68a91070 4001fe3 2c System.Int32 0 instance 0 _controlState
14:
68a85ea0 4001fe4 10 ...m.Web.UI.StateBag 0 instance 00000000 _viewState
15:
68a2af44 4001fe5 14 ...em.Web.UI.Control 0 instance 023e8864 _namingContainer
16:
68a273d0 4001fe6 18 System.Web.UI.Page 0 instance 01df4514 _page
17:
68a92e2c 4001fe7 1c ...+OccasionalFields 0 instance 00000000 _occasionalFields
18:
68a2b378 4001fe8 20 ...I.TemplateControl 0 instance 00000000 _templateControl
19:
68a14528 4001fe9 24 ...m.Web.VirtualPath 0 instance 00000000 _templateSourceVirtualDirectory
20:
68a8bb48 4001fea 28 ...rs.ControlAdapter 0 instance 00000000 _adapter
21:
68a3a8f8 4001feb 30 ...SimpleBitVector32 1 instance 023ea0d8 flags
22: 790f9c18 4001fda c70 System.Object 0 shared static EventDataBinding
23:
>> Domain:Value 000f0d00:NotInit 0011a720:01df0028 <<
24: 790f9c18 4001fdb c74 System.Object 0 shared static EventInit
25:
>> Domain:Value 000f0d00:NotInit 0011a720:01df0034 <<
26: 790f9c18 4001fdc c78 System.Object 0 shared static EventLoad
27:
>> Domain:Value 000f0d00:NotInit 0011a720:01df0040 <<
28: 790f9c18 4001fdd c7c System.Object 0 shared static EventUnload
29:
>> Domain:Value 000f0d00:NotInit 0011a720:01df004c <<
30: 790f9c18 4001fde c80 System.Object 0 shared static EventPreRender
31:
>> Domain:Value 000f0d00:NotInit 0011a720:01df0058 <<
32: 790f9c18 4001fdf c84 System.Object 0 shared static EventDisposed
33:
>> Domain:Value 000f0d00:NotInit 0011a720:01df0064 <<
34: 79124228 4001fec c88 System.Object[] 0 shared static automaticIDs
35:
>> Domain:Value 000f0d00:NotInit 0011a720:01df0070 <<
36:
790fa3e0 4002211 34 System.String 0 instance 02238664 _text
很好,我们再来看看其中的玄机。我们挑"text-property”来作为例子:它就在上面结果的最后一行,02238664是它在内存中的地址。我们使用下面的命令来获取它的值:
1: 0:000> !do 02238664
2:
Name: System.String
3:
MethodTable: 790fa3e0
4:
EEClass: 790fa340
5:
Size: 158(0x9e) bytes
6:
GC Generation: 2
7:
(C:/WINDOWS/assembly/GAC_32/mscorlib/2.0.0.0__b77a5c561934e089/mscorlib.dll)
8:
String: </td>
9:
</tr>
10:
</table>
11:
<!-- end of content table -->
12:
13:
Fields:
14:
MT Field Offset Type VT Attr Value Name
15:
790fed1c 4000096 4 System.Int32 0 instance 71 m_arrayLength
16:
790fed1c 4000097 8 System.Int32 0 instance 70 m_stringLength
17:
790fbefc 4000098 c System.Char 0 instance 3c m_firstChar
18: 790fa3e0 4000099 10 System.String 0 shared static Empty
19:
>> Domain:Value 000f0d00:790d6584 0011a720:790d6584 <<
20: 79124670 400009a 14 System.Char[] 0 shared static WhitespaceChars
21:
>> Domain:Value 000f0d00:01d413b8 0011a720:01d44f80 <<
从中我们发现这个string表示了一个HTML中关闭Table的字符。如果有需要我们也可以查看对象中其它的一些属性。不过下面我们先来介绍一下另外一个非常有用的命令。。。
使用!objsize
有没有办法获取一个特指的System.Web.UI.LiteralControl的大小呢?很简单,我们可以使用!objsize这个命令。!objsize会查看对象内所有的引用,并且计算它们总的大小。下面是内置帮助文档对!objsize 命令的介绍:
1:
0:000> !help objsize
2:
-------------------------------------------------------------------------------
3:
!ObjSize [<Object address>]
4:
5:
With no parameters, !ObjSize lists the size of all objects found on managed
6: threads. It also enumerates all GCHandles in the process, and totals the size
7: of any objects pointed to by those handles. In calculating object size,
8: !ObjSize includes the size of all child objects in addition to the parent.
我们来做个试验,以一个Customer对象为例,首先看!dumpobj的结果:
1: 0:000> !do a79d40
2:
Name: Customer
3:
MethodTable: 009038ec
4:
EEClass: 03ee1b84
5:
Size: 20(0x14) bytes
6:
(C:/pub/unittest.exe)
7:
Fields:
8:
MT Field Offset Type Attr Value Name
9:
009038ec 4000008 4 CLASS instance 00a79ce4 name
10:
009038ec 4000009 8 CLASS instance 00a79d2c bank
11:
009038ec 400000a c System.Boolean instance 1 valid
下面是!objsize:
1:
0:000> !ObjSize a79d40
2: sizeof (00a79d40) = 152 ( 0x98) bytes (Customer)
!do的结果是20字节,而!objsize的结果是152字节。
这是因为一个Customer对象包含了一个Bank对象,一个name,而一个Bank对象又包含了Address对象。你可以使用!objsize来查看一个大对象,比如web server中的managed cache.
托管堆中的对象也可能比看起来的要小
看看我们使用!objsize来查看LiteralControl的结果是什么?有意思的是,调试器得忙碌好一会儿,才最终得到了这样的结果:
1:
0:000> !objsize 023ea0a8
2: sizeof (023ea0a8) = 456918136 ( 0x1b3c0478) bytes (System.Web.UI.LiteralControl)
456M!这真的有必要么?不过我们再来看看前面使用过的!do命令查看LiteralControl的结果就明白了。LiteralControl包含了一个page对象。而page对象又包含了cache对象,以至于我们几乎看到了整个程序所使用到的托管堆的大小。
总结
记住这三个命令:
1. !dumpheap
2. !dumpobj
3. !objsize
它们会在你高喊SOS的时候很好的帮你脱离困境。
此文完。