Windbg检查托管代码的内存泄露

来源:互联网 发布:java链接oracle数据库 编辑:程序博客网 时间:2024/05/01 06:16
 

在写托管代码的过程中,有一些地方很容易造成程序的内存持续增长,直到程序结束时才能释放,下面以一个测试程序为例子讲述怎么检查托管代码的内存泄露:

 

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时有内存泄露。按照网上提供的解决方案可以解决这个问题

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            WalkDictionary(this.Resources);
 
            base.OnStartup(e);
        }
 
        private static void WalkDictionary(ResourceDictionary resources)
        {
            foreach (DictionaryEntry entry in resources)
            {
            }
 
            foreach (ResourceDictionary rd in resources.MergedDictionaries)
                WalkDictionary(rd);
        }
    }

具体可以看这个地址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