现在的位置: 首页 > 综合 > 正文

.net framework中如何解决OutOfMemoryExceptions

2013年10月12日 ⁄ 综合 ⁄ 共 6966字 ⁄ 字号 评论关闭

遇到了问题:

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

此文完。

 

舶来

抱歉!评论已关闭.