• .NET Debugging Demos Lab 7: Memory Leak


    https://www.tessferrandez.com/blog/2008/04/03/net-debugging-demos-lab-7-walkthrough.html

    We have reached the end of the .NET Debugging Labs series. And we are going to end it with a walkthrough of the last memory leak lab.

    I am interested to get feedback, either in comments or through email in the contact me section, on what you liked, what you didn’t like. If you felt that it was worth your while etc. so I know if there is an interest in possibly doing similar lab series in the future. It will probably be a while before I would post a new series but if there is interest at least I can start thinking about it. If that is the case, let me know what you thought was missing from this one so that I can put some good scenarios in the next one.

    Previous labs and setup instructionsPermalink

    If you are new to the debugging labs, here you can find information on how to set up the labs as well as links to the previous labs in the series.

    Review the performance counters to figure out what we are leakingPermalink

    1. Check Working SetGC Heap Size and Number of Assemblies Loaded

      • Can you tell if the issue we are facing is a virtual bytes leak, a native leak or a .NET leak?

        The numbers seem to follow each other so it looks like a .net memory issue

    Debug the memory dumpPermalink

    1. Open the memory dump in WinDbg, load up the symbols and load sos.dll (see information and setup instructions for more info)

      • What is the size of the memory dump (on disk)

        ~770 MB

    2. Run !eeheap -gc and !dumpheap -stat

       0:000> !eeheap -gc
       Number of GC Heaps: 8
       ------------------------------
       Heap 0 (000001EF5C8E8EE0)
       generation 0 starts at 0x000001EF5CD66400
       generation 1 starts at 0x000001EF5CCA1018
       generation 2 starts at 0x000001EF5CCA1000
       ephemeral segment allocation context: none
               segment             begin         allocated              size
       000001EF5CCA0000  000001EF5CCA1000  000001EF5CD7A418  0xd9418(889880)
       Large object heap starts at 0x000001F35CCA1000
               segment             begin         allocated              size
       000001F35CCA0000  000001F35CCA1000  000001F35FB9AEC0  0x2ef9ec0(49258176)
       Heap Size:       Size: 0x2fd32d8 (50148056) bytes.
       ------------------------------
       Heap 1 (000001EF5C9100E0)
       generation 0 starts at 0x000001EFDCD02A08
       generation 1 starts at 0x000001EFDCCA1018
       generation 2 starts at 0x000001EFDCCA1000
       ephemeral segment allocation context: none
               segment             begin         allocated              size
       000001EFDCCA0000  000001EFDCCA1000  000001EFDCEF4A20  0x253a20(2439712)
       Large object heap starts at 0x000001F36CCA1000
               segment             begin         allocated              size
       000001F36CCA0000  000001F36CCA1000  000001F36FB2CBC8  0x2e8bbc8(48806856)
       Heap Size:       Size: 0x30df5e8 (51246568) bytes.
       ------------------------------
       ...
       ------------------------------
       Heap 7 (000001F3E86D84F0)
       generation 0 starts at 0x000001F2DCD80CA8
       generation 1 starts at 0x000001F2DCCA1018
       generation 2 starts at 0x000001F2DCCA1000
       ephemeral segment allocation context: none
               segment             begin         allocated              size
       000001F2DCCA0000  000001F2DCCA1000  000001F2DCD8ECC0  0xedcc0(974016)
       Large object heap starts at 0x000001F3CCCA1000
               segment             begin         allocated              size
       000001F3CCCA0000  000001F3CCCA1000  000001F3CFB8EAB8  0x2eedab8(49207992)
       Heap Size:       Size: 0x2fdb778 (50182008) bytes.
       ------------------------------
       GC Heap Size:    Size: 0x18c53f18 (415579928) bytes.
      
      • What is the size of the .NET heap according to !eeheap -gc?

        Around 415 MB - so quite a bit of the memory goes here

      • Is most of the memory stored on the regular heaps or on the large object heap?

        Looks like the large object heap is very heavily used

      We saw from the performance counters that we appeared to be leaking .net memory, so the next step is to determine what the memory is used for.

    3. Run !dumpheap -stat

       0:000> !dumpheap -stat
       Statistics:
                   MT    Count    TotalSize Class Name
       ...
       00007ffcfcadbe00     1002       104208 BuggyBits.Controllers.NewsController
       00007ffcfc3997a8     1472       105984 System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]]
       00007ffcfcc509f0     1698       108672 System.Collections.Generic.HashSet`1[[System.Object, System.Private.CoreLib]]
       00007ffcfcd62400     1849       118336 Microsoft.AspNetCore.Mvc.Routing.UrlActionContext
       00007ffcfcd48fd0     3007       120280 Microsoft.Extensions.Internal.CopyOnWriteDictionary`2[[System.Object, System.Private.CoreLib],[System.Object, System.Private.CoreLib]]
       00007ffcfcc687c0     2556       122688 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Object, System.Private.CoreLib],[Microsoft.Extensions.Caching.Memory.CacheEntry, Microsoft.Extensions.Caching.Memory]]
       00007ffcfce031a0     1421       125048 System.Diagnostics.Tracing.IncrementingCounterPayload
       00007ffcfccad320     3938       126016 Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.ViewBufferPage
       00007ffcfcba0298     1002       128256 Microsoft.AspNetCore.Http.DefaultHttpContext
       00007ffcfcd60768     1853       129800 Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.ViewBufferPage[]
       00007ffcfcd49dd8     3488       139520 Microsoft.AspNetCore.Razor.TagHelpers.TagHelperAttribute
       00007ffcfcb6fd10      232       142912 Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1[[Microsoft.AspNetCore.Hosting.HostingApplication+Context, Microsoft.AspNetCore.Hosting]]
       000001ef5c3a6b70     4825       145952      Free
       00007ffcfcd46dc0     2325       148800 System.Func`1[[System.Threading.Tasks.Task, System.Private.CoreLib]]
       00007ffcfcbbafc0     3468       155344 Microsoft.AspNetCore.Routing.RouteEndpoint[]
       00007ffcfc4c8f58     1852       162800 System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]][]
       00007ffcfcd68c80     1851       162824 Microsoft.AspNetCore.Routing.Tree.OutboundMatchResult[]
       00007ffcfcd601d0     4168       166720 Microsoft.AspNetCore.Razor.TagHelpers.DefaultTagHelperContent
       00007ffcfc3f0db0     1747       181688 System.Reflection.RuntimeMethodInfo
       00007ffcfcc67a68     1007       193344 Microsoft.Extensions.Caching.Memory.CacheEntry
       00007ffcfcaed2e0     3936       220416 Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.ViewBuffer
       00007ffcfcadde08     1856       222720 Microsoft.AspNetCore.Mvc.TagHelpers.AnchorTagHelper
       00007ffcfc2d6618      560       224136 System.Object[]
       00007ffcfc392aa8     3873       233464 System.SByte[]
       00007ffcfcdfb628     2436       253344 System.Diagnostics.Tracing.CounterPayload
       00007ffcfcc88000     3934       314720 Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers.ViewBufferTextWriter
       00007ffcfc3d3058      995       358016 System.Char[]
       00007ffcfc9e4fa8     9122       364880 Microsoft.AspNetCore.Routing.RouteValueDictionary
       00007ffcfcb53bf8     8922       691920 System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]][]
       00007ffcfc392360      457       731633 System.Byte[]
       00007ffcfc391e18    16450      1108462 System.String
       00007ffcfc3af090     2352    400900028 System.Int32[]
      
      • What types of objects seem to use up most of the memory?

        out of the approximately 417 MB, 400 MB are locked up in Int32 arrays, so if we could just figure out what is causing these (or the largest of these arrays) to stick around we would probably resolve the issue.

      • Looking at the 10-20 bottommost object types in !dumpheap -stat, can you see any patterns among the objects? I.e. can they be grouped in any way?

        Most of the 10-20 bottommost object types are UI or request related, i.e. objects that you would typically see hooked up to an asp.net page or an asp.net request.

      Normally you will be able to see patterns such as many data related objects, many ui related objects, many xml related objects etc. like in this memory investigation

      • Looking at the 10-20 bottom most object types in !dumpheap -stat, do the quantities of each of them seem normal or does anything seem out of the ordinary?

        The most obvious one is the Int32 arrays, but the fact that we have 1002 NewsController objects is also very curious. We ran the test with 1000 requests, and I had browsed to the News part once or twice before running the test to start up the iisexpress.exe process so it looks like all the requests are still sticking around. Normally you should expect to see only the requests that are currently executing stick around or perhaps a few more, but definitely not this many. If I run ~* e !clrstack to see all .net stacks I have no requests currently executing.

    4. Dump the large objects using !dumpheap -min 0n85000

       0:000> !dumpheap -min 0n85000
               Address               MT     Size
       ...
       000001f3cfa69ab0 00007ffcfc3af090   400024
       000001f3cfacb568 00007ffcfc3af090   400024
       000001f3cfb2d020 00007ffcfc3af090   400024
      
       Statistics:
                   MT    Count    TotalSize Class Name
       00007ffcfc392360        1       131096 System.Byte[]
       00007ffcfc3af090     1002    400824048 System.Int32[]
       Total 1003 objects
      
      • What type of objects are stored on the Large Object Heap (LOH)?

        Most (or all in this case) LOH objects are Int32 arrays and they all seem to have approximately the same size. What is even more interesting is that there are 1002 of them (same number as our aspx pages) so they could be connected somehow.

    5. Take any of the objects on the LOH and run !gcroot <object address> to find out where they are rooted

       0:000> !gcroot 000001f3cfb2d020
       Thread 5560:
           0000007B1C5FF050 00007FFD5B7A3542 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [/_/src/System.Private.CoreLib/shared/System/Threading/Tasks/Task.cs @ 2922]
               rbp+10: 0000007b1c5ff0c0
                   ...
                   ->  000001F15CD231D8 System.Collections.Concurrent.ConcurrentDictionary`2+Tables[[System.Object, System.Private.CoreLib],[Microsoft.Extensions.Caching.Memory.CacheEntry, Microsoft.Extensions.Caching.Memory]]
                   ->  000001F15CD103F8 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Object, System.Private.CoreLib],[Microsoft.Extensions.Caching.Memory.CacheEntry, Microsoft.Extensions.Caching.Memory]][]
                   ->  000001F0DCE337A0 System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Object, System.Private.CoreLib],[Microsoft.Extensions.Caching.Memory.CacheEntry, Microsoft.Extensions.Caching.Memory]]
                   ->  000001F0DCE33450 Microsoft.Extensions.Caching.Memory.CacheEntry
                   ->  000001F0DCE33690 System.Collections.Generic.List`1[[Microsoft.Extensions.Caching.Memory.PostEvictionCallbackRegistration, Microsoft.Extensions.Caching.Abstractions]]
                   ->  000001F0DCE336B0 Microsoft.Extensions.Caching.Memory.PostEvictionCallbackRegistration[]
                   ->  000001F0DCE33670 Microsoft.Extensions.Caching.Memory.PostEvictionCallbackRegistration
                   ->  000001F0DCE33630 Microsoft.Extensions.Caching.Memory.PostEvictionDelegate
                   ->  000001F0DCE33140 BuggyBits.Controllers.NewsController
                   ->  000001F3CFB2D020 System.Int32[]
      
       Found 1 unique roots (run '!gcroot -all' to see all roots).
      
      • What types of roots would you normally see when running !gcroot and what do they mean?

        GCRoot will check all “roots” in the process and if the object you are looking for is in any of the “root chains” it will list this root chain.

      Look at the root chain of the object that you did !gcroot on.

      • Why is it sticking around? How could the root chain be broken?

        The Int32[] is a member variable of a news_aspx page, and this in turn is a member variable of a CacheItemRemovedCallback which is referenced by a CacheEntry so as long as the cache entry exists and the link is not severed the object will stay around. The question is really what is a CacheItemRemovedCallback and why is the page referenced there…

        Let’s look at the objects involved in a bit more detail…

          0:000> !do 000001F0DCE33450
          Name:        Microsoft.Extensions.Caching.Memory.CacheEntry
          MethodTable: 00007ffcfcc67a68
          EEClass:     00007ffcfcc735b0
          Size:        192(0xc0) bytes
          File:        C:Program FilesdotnetsharedMicrosoft.AspNetCore.App3.1.11Microsoft.Extensions.Caching.Memory.dll
          Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
          ...
          00007ffcfc2d0af0  4000010       38        System.Object  0 instance 000001f0dce336e8 _value
          ...
          00007ffcfc2d0af0  4000014       48        System.Object  0 instance 000001f0dce333b0 <Key>k__BackingField
          ....
          00007ffcfc49b420  4000004       10 ...Private.CoreLib]]  0   static 000001f25cd15500 ExpirationCallback
          0:000> !DumpObj /d 000001f0dce336e8
          Name:        System.String
          MethodTable: 00007ffcfc391e18
          EEClass:     00007ffcfc382150
          Size:        78(0x4e) bytes
          File:        C:Program FilesdotnetsharedMicrosoft.NETCore.App3.1.11System.Private.CoreLib.dll
          String:      New site launched 2008-02-02
          Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
          00007ffcfc2db1f0  4000242        8         System.Int32  1 instance               28 _stringLength
          00007ffcfc2d8008  4000243        c          System.Char  1 instance               4e _firstChar
          00007ffcfc391e18  4000244      110        System.String  0   static 000001f2dcca1360 Empty
          0:000> !DumpObj /d 000001f0dce333b0
          Name:        System.String
          MethodTable: 00007ffcfc391e18
          EEClass:     00007ffcfc382150
          Size:        94(0x5e) bytes
          File:        C:Program FilesdotnetsharedMicrosoft.NETCore.App3.1.11System.Private.CoreLib.dll
          String:      3f98f161-6b7a-4144-8ba6-6f2f11892501
          Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
          00007ffcfc2db1f0  4000242        8         System.Int32  1 instance               36 _stringLength
          00007ffcfc2d8008  4000243        c          System.Char  1 instance               33 _firstChar
          00007ffcfc391e18  4000244      110        System.String  0   static 000001f2dcca1360 Empty
        

        The CacheEntry has the cache key 3f98f161-6b7a-4144-8ba6-6f2f11892501 and the value New site launched 2008-02-02.

        Looking at the NewsController we find

          public IActionResult Index()
          {
              string key = Guid.NewGuid().ToString();
              var cachedResult = cache.GetOrCreate(key, cacheEntry => {
                  cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(5);
                  cacheEntry.RegisterPostEvictionCallback(CacheRemovedCallback);
                  cacheEntry.Priority = CacheItemPriority.NeverRemove;
        
                  return new string("New site launched 2008-02-02");
              });
        
              var news = new List<News>
              {
                  new News() { Title = cachedResult }
              };
              return View(news);
          }
        

        The next item in the root chain was the PostEvictionDelegate and that is linked to the CacheEntry as the _target, looking at the code above we see that we do in fact register a PostEvictionCallback in the Cache.Add method, so that explains why it is linked like this.

          0:000> !do 000001F0DCE33630
          Name:        Microsoft.Extensions.Caching.Memory.PostEvictionDelegate
          MethodTable: 00007ffcfcdd9450
          EEClass:     00007ffcfcdce548
          Size:        64(0x40) bytes
          File:        C:Program FilesdotnetsharedMicrosoft.AspNetCore.App3.1.11Microsoft.Extensions.Caching.Abstractions.dll
          Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
          00007ffcfc2d0af0  400012a        8        System.Object  0 instance 000001f0dce33140 _target
          00007ffcfc2d0af0  400012b       10        System.Object  0 instance 0000000000000000 _methodBase
          00007ffcfc2decc8  400012c       18        System.IntPtr  1 instance 00007FFCFCA0A1E8 _methodPtr
          00007ffcfc2decc8  400012d       20        System.IntPtr  1 instance 0000000000000000 _methodPtrAux
          00007ffcfc2d0af0  40001bc       28        System.Object  0 instance 0000000000000000 _invocationList
          00007ffcfc2decc8  40001bd       30        System.IntPtr  1 instance 0000000000000000 _invocationCount
        

        The target member variable points to a NewsController. The _target member variable for a delegate/event handler is the object instance that should be used to call the event handler.

        Going back to the code above, the method that handles the PostEvictionCallback (i.e. the function to be called when the cached object is removed from cache) is this.CacheRemovedCallback. In other words, our target is _this (which is the NewsController itself, and the _methodPtr should point to CacheRemovedCallback. So the Controller will be linked to the cacheEntry as long as it is still in cache in order to be able to call the CacheRemovedCallback when it goes out of scope.

        Finally - the Int32[] is just a member variable of the NewsController

    Putting it all together and determining the cause of the memory leakPermalink

    1. Look at the code for the News controller and use the knowledge you gathered from the debugging session to figure out how the leak was generated.

      • How long will the objects stick around?

        Until the object is dropped from cache, in this case 5 minutes

      • What can you do to avoid this type of event handler leak?

        Any time you hook up an event handler to a static object or a long lived object it is better to hook up a static method as the event handler rather than an instance method. That way there is no object that needs to stick around for the event handler to still be valid.

        In this particular case, every time we hit the News page and add the item to cache we will “store” an instance of the NewsController in cache and everything it references. The easy solution here would be to make the CacheRemovedCallback static and call NewsController.CacheRemovedCallback rather than this.CacheRemovedCallback.

        I have described this particular issue and another similar one in the following articles:

        You should also be careful to not store web controls or any other objects in cache that may have references that you are not aware of. Storing a web control in cache for example will also store a page in cache since the web control usually has a _parent property that points to the aspx page.

    Have fun, Tess

     Tags:   

     Categories: 

     Updated: April 3, 2008

  • 相关阅读:
    DATAX windows下安装及csv文件导入mysql简单案例
    JAVA质量属性之易用性战术分析
    以《淘宝网》为例,描绘质量属性的六个常见属性场景
    14 得到相关连的热词数据
    13 信息领域热词分析部分功能展示
    12 所学和感受
    从Android开发看MVC
    论面向服务架构(SOA)设计及其应用
    GitHub上快速创建文件夹
    GitHub将私有库(private)、公有库(public)相互转换
  • 原文地址:https://www.cnblogs.com/micro-chen/p/14823049.html
Copyright © 2020-2023  润新知