在写托管代码的过程中,有一些地方很容易造成程序的内存持续增长,直到程序结束时才能释放,下面以一个测试程序为例子讲述怎么检查托管代码的内存泄露:
1. 运行测试程序TestCLRMemoryLeak.exe,运行Windbg,并Attach该程序。此时程序的Heap大小为1640372,继续运行程序一段时间
0:007> .loadby sos mscorwks
0:007> !eeheap
Loader Heap:
... ...
GC Heap Size 0x1907b4(1640372)
0:007> g
2. 此时程序的Heap内存已经变的大很多哦
0:008> !eeheap
Loader Heap:
... ...
GC Heap Size 0x11e9188(18780552)
3. 因此需要检查有什么对象没有被释放
0:008> !dumpheap -stat
total 181593 objects
Statistics:
MT Count TotalSize Class Name
01040054 579 148224 TestCLRMemoryLeak.Page1
... ...
4. 哇,TestCLRMemoryLeak.Page1, 这个类的对象都是临时对象,怎么会有这么多在内存中呢?挑其中一个对象分析一下
0:008> !dumpheap -mt 01040054
Address MT Size
01961f68 01040054 256
0196a800 01040054 256
... ...
0:008> !gcroot 0196a800
... ...
ESP:30f220:Root:018c26c4(TestCLRMemoryLeak.App)->
018cc740(System.Windows.ResourceDictionary)->
01962cf8(System.Collections.Generic.List`1[[System.Windows.DeferredResourceReference, PresentationFramework]])->
07d5fb70(System.Object[])->
0196ad2c(System.Windows.DeferredAppResourceReference)->
0196ad44(System.EventHandler)->
0196acf0(System.Windows.ResourceReferenceExpression)->
0196a800(TestCLRMemoryLeak.Page1)
... ...
0:008> !do 0196acf0
... ...
Fields:
MT Field Offset Type VT Attr Value Name
6495ea94 4000dfb 4 System.Int32 1 instance 8 _flags
6369061c 4000dfa 1d0 System.Object 0 static 018f71b0 NoValue
6369061c 40026ca 8 System.Object 0 instance 0196acd0 _resourceKey
... ...
0:008> !do 0196acd0
Name: System.String
MethodTable: 63690a00
EEClass: 6344d64c
Size: 32(0x20) bytes
(D:/WINDOWS/assembly/GAC_32/mscorlib/2.0.0.0__b77a5c561934e089/mscorlib.dll)
String: MyBrush
5. 发现这个问题是由于Page1用了资源MyBrush造成的。从网上搜索可以知道,WPF在使用DynamicResource时有内存泄露。按照网上提供的解决方案可以解决这个问题
具体可以看这个地址http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b97a5f83-5394-430e-9a78-9d3a957e3537/
6. 修改后继续检查发现Page1还是没有释放,我们需要再挑其中一个对象分析一下
0:008> !gcroot 01a13d38
... ...
ESP:1cec0c:Root:019784c4(System.Windows.Threading.Dispatcher)->
01991dbc(System.Windows.Input.InputManager)->
01992464(System.Windows.Input.StylusLogic)->
01992598(System.Collections.Generic.Dictionary`2[[System.Object, mscorlib],[System.Windows.Input.PenContexts, PresentationCore]])->
019925e4(System.Collections.Generic.Dictionary`2+Entry[[System.Object, mscorlib],[System.Windows.Input.PenContexts, PresentationCore]][])->
019e0508(System.Windows.Interop.HwndSource)->
01980550(TestCLRMemoryLeak.Window1)->
019e0370(System.Windows.EffectiveValueEntry[])->
01a1c778(System.Windows.EventHandlersStore)->
01a1c7a4(MS.Utility.SingleObjectMap)->
01a1c784(MS.Utility.FrugalObjectList`1[[System.Windows.RoutedEventHandlerInfo, PresentationCore]])->
01a4c9c8(MS.Utility.ArrayItemList`1[[System.Windows.RoutedEventHandlerInfo, PresentationCore]])->
01c7e1ac(System.Windows.RoutedEventHandlerInfo[])->
01a1c758(System.Windows.RoutedEventHandler)->
01a13d38(TestCLRMemoryLeak.Page1)
... ...
0:008> !do 01a1c758
Name: System.Windows.RoutedEventHandler
MethodTable: 598b5118
EEClass: 59672958
Size: 32(0x20) bytes
(D:/WINDOWS/assembly/GAC_32/PresentationCore/3.0.0.0__31bf3856ad364e35/PresentationCore.dll)
Fields:
MT Field Offset Type VT Attr Value Name
6369061c 40000ff 4 System.Object 0 instance 01a13d38 _target
6368fe74 4000100 8 ...ection.MethodBase 0 instance 00000000 _methodBase
636932c8 4000101 c System.IntPtr 1 instance 93c270 _methodPtr
636932c8 4000102 10 System.IntPtr 1 instance 0 _methodPtrAux
6369061c 400010c 14 System.Object 0 instance 00000000 _invocationList
636932c8 400010d 18 System.IntPtr 1 instance 0 _invocationCount
0:008> !IP2MD 93c270
Failed to request MethodData, not in JIT code range
0:008> !U 93c270
Unmanaged code
0093c270 e8455b0e64 call mscorwks!PrecodeFixupThunk (64a21dba)
0093c275 5e pop esi
0093c276 0000 add byte ptr [eax],al
0093c278 fc cld
0093c279 9b wait
0093c27a 93 xchg eax,ebx
0093c27b 0000 add byte ptr [eax],al
0093c27d 0000 add byte ptr [eax],al
0093c27f 0000 add byte ptr [eax],al
0093c281 0000 add byte ptr [eax],al
7. 咦,找不到这个函数,难道没有被加载?
0:008> dd 93c270
0093c270 0e5b45e8 00005e64 00939bfc 00000000
0093c280 00000000 00000000 00000000 00000000
0093c290 00000000 00000000 00000000 00000000
0093c2a0 00000000 00000000 00000000 00000000
0093c2b0 00000000 00000000 00000000 00000000
0093c2c0 00000000 00000000 00000000 00000000
0093c2d0 00000000 00000000 00000000 00000000
0093c2e0 00000000 00000000 00000000 00000000
0:008> !dumpmd 00939bfc
Method Name: TestCLRMemoryLeak.Page1.MainWindow_LostFocus(System.Object, System.Windows.RoutedEventArgs)
Class: 00b61904
MethodTable: 01790054
mdToken: 0600002a
Module: 00932c5c
IsJitted: no
CodeAddr: ffffffff
8. OK,就是这个函数,程序中加了MainWindow_LostFocus,但是没有相应的地方减去该函数,代码如下
public partial class Page1 : Page
{
private int lostFocusCount = 0;
public Page1()
{
InitializeComponent();
App.Current.MainWindow.LostFocus += new RoutedEventHandler(MainWindow_LostFocus);
}
void MainWindow_LostFocus(object sender, RoutedEventArgs e)
{
++lostFocusCount;
Trace.WriteLine("MainWindow_LostFocus");
}
}
9. 经检查发现函数MainWindow_LostFocus还没有被JIT编译加载,所以上述第7步需要通过DD命令才能找到对应的函数。该函数被调用后再用命令检查结果如下:
0:008> !IP2MD c4c120
Failed to request MethodData, not in JIT code range
0:008> !U c4c120
Unmanaged code
00c4c120 e953460a00 jmp 00cf0778
00c4c125 5f pop edi
00c4c126 0000 add byte ptr [eax],al
00c4c128 98 cwde
00c4c129 8ac4 mov al,ah
00c4c12b 0000 add byte ptr [eax],al
00c4c12d 0000 add byte ptr [eax],al
00c4c12f 0000 add byte ptr [eax],al
00c4c131 0000 add byte ptr [eax],al
00c4c133 0000 add byte ptr [eax],al
0:008> !dumpmd 00cf0778
00cf0778 is not a MethodDesc
0:008> !IP2MD 00cf0778
MethodDesc: 00c48a98
Method Name: TestCLRInlineCode.Page1.MainWindow_CollectionChanged(System.Object, System.EventArgs)
Class: 00d70e38
MethodTable: 00c48b00
mdToken: 06000015
Module: 00c42c5c
IsJitted: yes
CodeAddr: 00cf0778