1.用windbg或者VS加载SOS.dll, 可以参考我的另外一篇“如何用Visual Studio集成SOS.dll进行调试”
2.
(1)用GCHandleLeaks调查
具体示例如下:
0:003> **!gchandleleaks**
-------------------------------------------------------------------------------
GCHandleLeaks will report any GCHandles that couldn't be found in memory.
Strong and Pinned GCHandles are reported at this time. You can safely abort the
memory scan with Control-C or Control-Break.
-------------------------------------------------------------------------------
Found 249 handles:
0000000001dc1200 0000000001dc1208 0000000001dc1210 0000000001dc1218
0000000001dc1220 0000000001dc1228 0000000001dc1230 0000000001dc1238
0000000001dc1240 0000000001dc1248 0000000001dc1250 0000000001dc1258
0000000001dc1260 0000000001dc1268 0000000001dc1270 0000000001dc1278
0000000001dc1280 0000000001dc1288 0000000001dc1290 0000000001dc1298
0000000001dc12a0 0000000001dc12a8 0000000001dc12b0 0000000001dc12b8
…*(the list goes on)*
Searching memory
Found 0000000001dc1ff8 at location 000000000012ce48
Found 0000000001dc1380 at location 0000000000145870
Found 0000000001dc1390 at location 0000000000145918
Found 0000000001dc1378 at location 00000000001557f8
…
------------------------------------------------------------------------------
Some handles were not found. If the number of not-found handles grows over the
lifetime of your application, you may have a GCHandle leak. This will cause
the GC Heap to grow larger as objects are being kept alive, referenced only
by the orphaned handle. If the number doesn't grow over time, note that there
may be some noise in this output, as an unmanaged application may be storing
the handle in a non-standard way, perhaps with some bits flipped. The memory
scan wouldn't be able to find those.
------------------------------------------------------------------------------
Didn't find 232 handles:
0000000001dc1200 0000000001dc1208 0000000001dc1210 0000000001dc1218
0000000001dc1220 0000000001dc1228 0000000001dc1230 0000000001dc1238
0000000001dc1240 0000000001dc1248 0000000001dc1250 0000000001dc1258
0000000001dc1260 0000000001dc1268 0000000001dc1270 0000000001dc1278
0000000001dc1280 0000000001dc1288 0000000001dc1290 0000000001dc1298
...
"Didn't find XXX handles"就是我们需要关注的有可能泄露的句柄, 我们选一个进行查看, 如下:
0:003> **!dumpobj poi(**0000000001dc1280)
Name: BloatedObject
MethodTable: 00000642801315c0
EEClass: 0000064280163cd0
Size: 32(0x20) bytes
(C:\pub\eetwc\windowh.exe)
Fields:
MT Field Offset Type VT Attr Value Name
000006423756d248 4000001 8 System.IO.TextWriter 0 instance 0000000010353c28 tw
00000642375dba38 4000002 10 System.Byte[] 0 instance 0000000022e50080 **ba**
(2)利用dumpheap -stat和dumpobj
首先用dumpheap -stat列出所有类型的堆信息, 如下:
0:000> !dumpheap -stat
0x79c489a0 1 12 System.Runtime.Remoting.Messaging.ClientContextTerminatorSink
0x79bf9aec 1 12 System.IO.TextReader/NullTextReader
0x79be7078 1 12 System.Runtime.Remoting.Proxies.ProxyAttribute
0x79bce8e0 1 12 System.Runtime.InteropServices.ComVisibleAttribute
0x79bce7c8 1 12 System.CLSCompliantAttribute
0x79bc08e0 1 12 System.Empty
0x0618ae68 1 12 System.Web.Configuration.CustomErrorsConfigHandler
0x061887f8 1 12 System.Web.UI.WebControls.UnitConverter
0x06180848 1 12 System.Drawing.ColorConverter
0x05dbfbc4 1 12 System.Data.Res
第一列是地址,第二列是数量, 第三列是总共大小,第四列是具体类型名字, 需要注意的是第三列总共大小不包含成员变量的大小。
在其中找到异常的类型的堆地址, 比如有些类型的数量太大。 而后利用dumpobj来查看, 如下:
0:000> !dumpobj 0x05dbfbc4
Name: System.Data.DataSet
MethodTable 0x060bbd2c
EEClass 0x060d2614
Size 80(0x50) bytes
GC Generation: 2
mdToken: 0x0200003b (c:\windows\assembly\gac\system.data\1.0.5000.0__b77a5c561934e089\system.data.dll)
FieldDesc*: 0x060bb358
MT Field Offset Type Attr Value Name
0x060b252c 0x4000583 0x4 CLASS instance 0x00000000 site
0x060b252c 0x4000584 0x8 CLASS instance 0x00000000 events
0x060b252c 0x4000582 0 CLASS shared static EventDisposed
>> Domain:Value 0x001192a0:NotInit 0x0017fc40:NotInit 0x044b7b28:0x1c357cb8 <<
0x060bbd2c 0x40003d3 0xc CLASS instance 0x00000000 defaultViewManager
0x060bbd2c 0x40003d4 0x10 CLASS instance 0x3920ee28 tableCollection
0x060bbd2c 0x40003d5 0x14 CLASS instance 0x3920ed9c relationCollection
0x060bbd2c 0x40003d6 0x18 CLASS instance 0x00000000 extendedProperties
0x060bbd2c 0x40003d7 0x1c CLASS instance 0x1c357c90 dataSetName
0x060bbd2c 0x40003d8 0x20 CLASS instance 0x182d0224 _datasetPrefix
0x060bbd2c 0x40003d9 0x24 CLASS instance 0x182d0224 namespaceURI
0x060bbd2c 0x40003da 0x40 System.Boolean instance 0 caseSensitive
0x060bbd2c 0x40003db 0x28 CLASS instance 0x14309a0c culture
0x060bbd2c 0x40003dc 0x41 System.Boolean instance 1 enforceConstraints
0x060bbd2c 0x40003dd 0x42 System.Boolean instance 0 fInReadXml
0x060bbd2c 0x40003de 0x43 System.Boolean instance 0 fInLoadDiffgram
0x060bbd2c 0x40003df 0x44 System.Boolean instance 0 fTopLevelTable
0x060bbd2c 0x40003e0 0x45 System.Boolean instance 0 fInitInProgress
0x060bbd2c 0x40003e1 0x46 System.Boolean instance 1 fEnableCascading
0x060bbd2c 0x40003e2 0x47 System.Boolean instance 0 fIsSchemaLoading
0x060bbd2c 0x40003e3 0x2c CLASS instance 0x00000000 rowDiffId
0x060bbd2c 0x40003e4 0x48 System.Boolean instance 0 fBoundToDocument
0x060bbd2c 0x40003e5 0x30 CLASS instance 0x00000000 onPropertyChangingDelegate
0x060bbd2c 0x40003e6 0x34 CLASS instance 0x00000000 onMergeFailed
0x060bbd2c 0x40003e7 0x38 CLASS instance 0x00000000 onDataRowCreated
0x060bbd2c 0x40003e8 0x3c CLASS instance 0x00000000 onClearFunctionCalled
0x060bbd2c 0x40003e9 0 CLASS shared static zeroTables
>> Domain:Value 0x0017fc40:NotInit 0x044b7b28:0x1c357c80 <<
如果想要查看对象的实际大小值,即也包含成员变量的大小。 请用!objsize 内存地址
我们也可以指定查看具体类型的堆地址, 请用!dumpheap -type 类型名
(3)使用clrstack和finalizeQueue
我们用clrstack查看当前gc中对象情况, 然后用finalizeQueue查看即将要被回收的对象情况, 两者对比下,数量不对或者没被回收的都有可能是内存泄露。
最后, 调查内存泄露是个费时费力还不一定有结果的事情, 祝愿大家都能找出具体问题。