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

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

 

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
内存泄漏是指在程序运行过程中,分配的内存没有被正确释放,导致内存占用不断增加,最终耗尽系统资源。使用Windbg可以帮助我们检测和定位内存泄漏问题。Windbg是微软提供的Windows下强大的调试工具,可以分析多种软件异常问题。使用Windbg监测内存泄漏的一般步骤包括以下几个步骤:首先,我们需要在程序运行时使用Windbg附加到目标进程;然后,通过Windbg的命令和扩展插件来监测和分析内存使用情况;最后,根据Windbg的输出结果来定位内存泄漏的具体位置和原因。需要注意的是,Windbg只能监测两个时间点的申请堆内存的变化量,并没有统计释放的堆内存,因此在分析结果时需要结合代码将其他几项过滤掉,最终确定发生内存泄漏的那一项。\[1\]\[2\]如果你想了解更多关于使用Windbg监测内存泄漏的详细步骤和技巧,可以参考相关的专栏文章列表,如VC++常用功能开发汇总和C++软件异常排查从入门到精通系列教程。\[3\] #### 引用[.reference_title] - *1* *2* *3* [使用Windbg定位Windows C++程序中的内存泄漏](https://blog.csdn.net/chenlycly/article/details/121295720)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值