.net framework中如何解决OutOfMemoryExceptions

遇到了问题:

当你在使用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的时候很好的帮你脱离困境。

此文完。

 

舶来

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值