我正在尝试识别WPF应用程序中的内存泄漏。该应用程序遭受高内存消耗,偶尔会抛出OutOfMemoryExceptions。但是,应用程序并不总是显示这些行为。该应用程序构建为x86目标.NET 4.0,并在x64操作系统上运行。
当内存负载很高时,我可以使用procdump.exe在计算机上创建内存转储。转储文件的大小约为1.7 GB。然后我尝试使用WinDbg进行分析。使用!address -summary 提供以下输出并显示大约1.7 GB的专用字节(MEM_COMMIT):
--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
<unclassified> 2430 623a1000 ( 1.535 Gb) 82.09% 76.74%
Image 1583 13dc5000 ( 317.770 Mb) 16.60% 15.52%
Free 516 8583000 ( 133.512 Mb) 6.52%
Stack 111 18a5000 ( 24.645 Mb) 1.29% 1.20%
TEB 37 25000 ( 148.000 kb) 0.01% 0.01%
NlsTables 1 23000 ( 140.000 kb) 0.01% 0.01%
ActivationContextData 11 14000 ( 80.000 kb) 0.00% 0.00%
CsrSharedMemory 1 5000 ( 20.000 kb) 0.00% 0.00%
PEB 1 1000 ( 4.000 kb) 0.00% 0.00%
--- Type Summary (for busy) ------ RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_PRIVATE 2144 58bbb000 ( 1.386 Gb) 74.16% 69.32%
MEM_IMAGE 1929 163bf000 ( 355.746 Mb) 18.58% 17.37%
MEM_MAPPED 102 8af3000 ( 138.949 Mb) 7.26% 6.78%
--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT 3206 6da16000 ( 1.713 Gb) 91.62% 85.65%
MEM_RESERVE 969 a057000 ( 160.340 Mb) 8.38% 7.83%
MEM_FREE 516 8583000 ( 133.512 Mb) 6.52%
--- Protect Summary (for commit) - RgnCount ----------- Total Size -------- %ofBusy %ofTotal
PAGE_READWRITE 1605 50166000 ( 1.251 Gb) 66.93% 62.57%
PAGE_EXECUTE_READ 271 11e83000 ( 286.512 Mb) 14.97% 13.99%
PAGE_READONLY 623 7335000 ( 115.207 Mb) 6.02% 5.63%
PAGE_READWRITE|PAGE_WRITECOMBINE 8 2710000 ( 39.063 Mb) 2.04% 1.91%
PAGE_WRITECOPY 328 15bf000 ( 21.746 Mb) 1.14% 1.06%
PAGE_EXECUTE_READWRITE 210 7ef000 ( 7.934 Mb) 0.41% 0.39%
PAGE_EXECUTE_WRITECOPY 75 181000 ( 1.504 Mb) 0.08% 0.07%
PAGE_READWRITE|PAGE_GUARD 82 b5000 ( 724.000 kb) 0.04% 0.03%
<unknown> 4 4000 ( 16.000 kb) 0.00% 0.00%
--- Largest Region by Usage ----------- Base Address -------- Region Size ----------
<unclassified> 44150000 22c0000 ( 34.750 Mb)
Image 802000 243f000 ( 36.246 Mb)
Free 7a740000 d80000 ( 13.500 Mb)
Stack 6e10000 fd000 (1012.000 kb)
TEB 7eed9000 1000 ( 4.000 kb)
NlsTables 7efb0000 23000 ( 140.000 kb)
ActivationContextData 50000 4000 ( 16.000 kb)
CsrSharedMemory 7efe0000 5000 ( 20.000 kb)
PEB 7efde000 1000 ( 4.000 kb)
使用!eeheap -gc 显示以下结果:
Number of GC Heaps: 1
generation 0 starts at 0x78747eb4
generation 1 starts at 0x78741000
generation 2 starts at 0x04781000
ephemeral segment allocation context: none
segment begin allocated size
04780000 04781000 0577fca4 0xffeca4(16772260)
...
78740000 78741000 78841ff4 0x100ff4(1052660)
Large object heap starts at 0x05781000
segment begin allocated size
05780000 05781000 06755a58 0xfd4a58(16599640)
...
52f80000 52f81000 53e07308 0xe86308(15229704)
Total Size: Size: 0x279b9d7c (664509820) bytes.
------------------------------
GC Heap Size: Size: 0x279b9d7c (664509820) bytes.
因此,托管堆只有大约633 MB,因此托管堆和专用字节之间存在大约1 GB的差距 - 我认为这意味着非托管泄漏。因为我们在应用程序中使用P / Invoke和COM interop可能是一个很好的起点。我正在尝试!heap -s ,它显示以下输出:
LFH Key : 0x2c96c29b
Termination on corruption : ENABLED
Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast
(k) (k) (k) (k) length blocks cont. heap
-----------------------------------------------------------------------------
Virtual block: 15f20000 - 15f20000 (size 00000000)
...
Virtual block: 69cd0000 - 69cd0000 (size 00000000)
02de0000 00000002 81152 66472 81152 738 397 9 56 1 LFH
00720000 00001002 1088 136 1088 16 9 2 0 0 LFH
00470000 00041002 256 4 256 2 1 1 0 0
Virtual block: 1b150000 - 1b150000 (size 00000000)
...
Virtual block: 6b0d0000 - 6b0d0000 (size 00000000)
00180000 00001002 15424 11932 15424 61 163 5 44 0 LFH
00030000 00001002 1088 136 1088 16 9 2 0 0 LFH
00680000 00041002 256 4 256 0 1 1 0 0
045e0000 00001002 15424 14092 15424 23 29 5 0 0 LFH
00410000 00041002 1280 308 1280 1 2 2 0 0 LFH
04590000 00041002 256 120 256 1 19 1 0 0 LFH
06820000 00001002 256 108 256 6 4 1 0 0 LFH
09d80000 00001002 256 120 256 1 12 1 0 0 LFH
09c80000 00001002 256 96 256 5 6 1 0 0 LFH
09d10000 00011002 256 12 256 9 5 1 0 0
09f50000 00001002 256 4 256 1 2 1 0 0
0a990000 00001002 3136 2444 3136 819 78 3 0 0 LFH
External fragmentation 33 % (78 free blocks)
0a8d0000 00001002 15424 11792 15424 3620 140 5 0 0 LFH
External fragmentation 30 % (140 free blocks)
0ad20000 00001002 256 156 256 81 2 1 0 0
0abe0000 00001002 256 104 256 11 6 1 0 0 LFH
125a0000 00001002 3136 2936 3136 520 102 3 0 0 LFH
External fragmentation 17 % (102 free blocks)
12330000 00001002 64 8 64 4 6 1 0 0
0cda0000 00001002 64 12 64 3 1 1 0 0
280e0000 00001003 256 76 256 49 13 1 0 bad
28e10000 00001003 256 4 256 2 1 1 0 bad
29900000 00001003 256 4 256 2 1 1 0 bad
40190000 00001003 256 4 256 2 1 1 0 bad
10dd0000 00001003 256 4 256 2 1 1 0 bad
5b0d0000 00001003 1280 648 1280 627 18 2 0 bad
5bc70000 00001003 256 4 256 2 1 1 0 bad
30e90000 00001003 256 4 256 2 1 1 0 bad
5be60000 00001003 256 4 256 2 1 1 0 bad
30e50000 00001003 256 4 256 2 1 1 0 bad
5b330000 00001002 64 8 64 5 1 1 0 0
5b450000 00001002 64 12 64 3 2 1 0 0
5b5a0000 00001002 64 12 64 1 4 1 0 0
5db00000 00001002 3136 1572 3136 6 13 3 0 0 LFH
610a0000 00001002 256 4 256 2 1 1 0 0
612d0000 00001003 64 8 64 2 2 1 0 bad
-----------------------------------------------------------------------------
如果我总结所有堆的大小(已提交),则总和远低于1 GB。为什么堆大小的总和与托管堆大小和专用字节之间的差距不相关?然后去哪儿?我可以采取哪些步骤来确定泄漏的来源?
谢谢,
马库斯
修改
!finalizequeue 显示以下结果:
SyncBlocks to be cleaned up: 3
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 99 finalizable objects (729e88bc->729e8a48)
generation 1 has 8 finalizable objects (729e889c->729e88bc)
generation 2 has 844319 finalizable objects (726b0020->729e889c)
Ready for finalization 1560068 objects (729e8a48->72fdc258)
切换到终结器线程并执行 kb 会返回以下结果:
ChildEBP RetAddr Args to Child
06a0f090 76cc149d 00000748 00000000 00000000 ntdll!NtWaitForSingleObject+0x15
06a0f0fc 76491194 00000748 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98
06a0f114 76491148 00000748 ffffffff 00000000 kernel32!WaitForSingleObjectExImplementation+0x75
06a0f128 75c87690 00000748 ffffffff 09692d60 kernel32!WaitForSingleObject+0x12
06a0f14c 75daa4d1 02e69078 02e743e0 06a0f258 ole32!GetToSTA+0xad [d:\w7rtm\com\ole32\com\dcomrem\chancont.cxx @ 133]
06a0f17c 75dacef0 06a0f244 06a0f36c 09692d60 ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x140 [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 4419]
06a0f25c 75ca9d01 09692d60 06a0f36c 06a0f354 ole32!CRpcChannelBuffer::SendReceive2+0xef [d:\w7rtm\com\ole32\com\dcomrem\channelb.cxx @ 4076]
06a0f2d8 75ca9b24 09692d60 06a0f36c 06a0f354 ole32!CAptRpcChnl::SendReceive+0xaf [d:\w7rtm\com\ole32\com\dcomrem\callctrl.cxx @ 603]
06a0f32c 75dace06 09692d60 06a0f36c 06a0f354 ole32!CCtxComChnl::SendReceive+0x1c5 [d:\w7rtm\com\ole32\com\dcomrem\ctxchnl.cxx @ 734]
06a0f348 7635420b 0973866c 06a0f398 763d0149 ole32!NdrExtpProxySendReceive+0x49 [d:\w7rtm\com\rpc\ndrole\proxy.cxx @ 1932]
06a0f354 763d0149 6bb28307 06a0f7a0 0700022b rpcrt4!NdrpProxySendReceive+0xe
06a0f768 75dac8e2 75cbfa10 75cc4670 06a0f7a0 rpcrt4!NdrClientCall2+0x1a6
06a0f788 75ca98ad 00000010 00000005 06a0f7c0 ole32!ObjectStublessClient+0xa2 [d:\w7rtm\com\rpc\ndrole\i386\stblsclt.cxx @ 474]
06a0f798 75cab641 0973866c 00000002 02e847d8 ole32!ObjectStubless+0xf [d:\w7rtm\com\rpc\ndrole\i386\stubless.asm @ 154]
06a0f7c0 75cab5ed 0973866c 00000000 00000000 ole32!RemoteReleaseRifRefHelper+0xa5 [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 6770]
06a0f7fc 75cab172 09704bac 02e69078 00000002 ole32!RemoteReleaseRifRef+0xb0 [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 6694]
06a0f880 75caa66e 09704bac 09704ba8 00000000 ole32!CStdMarshal::DisconnectCliIPIDs+0x2ec [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 3964]
06a0f8b0 75caa817 00000002 09704c50 09704ba8 ole32!CStdMarshal::Disconnect+0x1ba [d:\w7rtm\com\ole32\com\dcomrem\marshal.cxx @ 3273]
06a0f8cc 75caa781 09704ba8 06a0f8ec 75caaaf3 ole32!CStdIdentity::~CStdIdentity+0x8c [d:\w7rtm\com\ole32\com\dcomrem\stdid.cxx @ 312]
06a0f8d8 75caaaf3 00000001 0f144b38 0f1fbe44 ole32!CStdIdentity::`scalar deleting destructor'+0xd
06a0f8ec 75dad380 80000000 06a0f904 6f8a4db7 ole32!CStdIdentity::CInternalUnk::Release+0x6e [d:\w7rtm\com\ole32\com\dcomrem\stdid.cxx @ 767]
06a0f8f8 6f8a4db7 0f1fbe44 06a0f948 6f8a4e26 ole32!IUnknown_Release_Proxy+0x11 [d:\w7rtm\com\rpc\ndrole\proxy.cxx @ 1773]
06a0f904 6f8a4e26 0f1fbe44 6a4e83f9 0f144b38 clr!ReleaseTransitionHelper+0xe
06a0f948 6f8a4e8a 0f1fbe44 0f144b18 6a4e8331 clr!SafeReleaseHelper+0x87
06a0f980 6f9f8dc8 0f1fbe44 0f144b18 00000001 clr!SafeRelease+0x2f
06a0f998 6f9f8e96 6a4e8379 00000001 0f144b18 clr!RCW::ReleaseAllInterfaces+0x4b
06a0f9c8 6f9f8ec2 0f144b18 6a4e834d 00000001 clr!RCW::ReleaseAllInterfacesCallBack+0xc4
06a0f9fc 6f9f9105 06a0fa54 06a0fa38 6f9f85d3 clr!RCW::Cleanup+0x22
06a0fa08 6f9f85d3 0f144b18 6a4e8089 06a0fa60 clr!RCWCleanupList::ReleaseRCWListRaw+0x18
06a0fa38 6f9f8a8a 00000001 6a4e8039 6fe995c8 clr!RCWCleanupList::ReleaseRCWListInCorrectCtx+0x10a
06a0fa88 6f9e14f6 6a4e8065 00000000 02e19cb0 clr!RCWCleanupList::CleanupAllWrappers+0x83
06a0fad4 6f9df457 00000001 06a0fc20 6f8f76af clr!SyncBlockCache::CleanupSyncBlocks+0xde
06a0fae0 6f8f76af 02e19cb0 06a0fc20 02e012f8 clr!Thread::DoExtraWorkForFinalizer+0x3b
06a0faf4 6fa1f815 00000001 06a0fbd8 02e19cb0 clr!WKS::GCHeap::FinalizerThreadWorker+0x9d
06a0fb08 6fa1f897 06a0fc20 6a4e8109 06a0fc20 clr!Thread::DoExtraWorkForFinalizer+0x114
06a0fbb8 6fa1f952 06a0fc20 6a4e86a9 00000000 clr!Thread::ShouldChangeAbortToUnload+0x101
06a0fc18 6fa15c57 00000000 02e07688 00000000 clr!Thread::ShouldChangeAbortToUnload+0x399
06a0fc3c 6fa15c6a 6f8f7624 00000008 06a0fc84 clr!ManagedThreadBase_NoADTransition+0x35
06a0fc4c 6f9c0186 6f8f7624 6a4e8635 00000000 clr!ManagedThreadBase::FinalizerBase+0xf
06a0fc84 6fa1f618 00000000 00000000 00000000 clr!WKS::GCHeap::FinalizerThreadStart+0x10c
06a0fd1c 764933aa 02e012f8 06a0fd68 775f9f72 clr!Thread::intermediateThreadProc+0x4b
06a0fd28 775f9f72 02e012f8 71c8758e 00000000 kernel32!BaseThreadInitThunk+0xe
06a0fd68 775f9f45 6fa1f5d0 02e012f8 00000000 ntdll!__RtlUserThreadStart+0x70
06a0fd80 00000000 6fa1f5d0 02e012f8 00000000 ntdll!_RtlUserThreadStart+0x1b
我知道RCW应该被释放,因此需要切换到或停止主STA线程。然而,这会带来新的问题:如何找出最终确定的对象(RCW背后的类型)以及为什么 GetToSTA 必须等待(等待什么)?
答案 0 :(得分:2)
从RedGate下载ANTS内存分析器
http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/
它将为您提供在哪里分配对象的可视化映射,并将显示当前正在使用的非托管内存量。它会指出你正确的方向。
答案 1 :(得分:1)
我可以建议两个动作:
!FinalizeQueue
并检查可终结对象的数量。这两篇文章可能会有所帮助(我是作者):