.NET Debugging Demos Lab 3: Memory - Walkthrough

15 minute read

This is the walkthrough of Lab 3 in the Buggy Bits series

I have removed some parts of the original text to make the post a little bit shorter, but as usual, my comments are inline

Examine the counters

  • What are the values for GC Heap Size (MB), and Working Set (MB) after the test?

      [System.Runtime]
          % Time in GC since last GC (%)                                 1
          Allocation Rate (B / 1 sec)                                8 168
          CPU Usage (%)                                                  0
          Exception Count (Count / 1 sec)                                0
          GC Heap Size (MB)                                            650
          Gen 0 GC Count (Count / 1 sec)                                 0
          Gen 0 Size (B)                                               192
          Gen 1 GC Count (Count / 1 sec)                                 0
          Gen 1 Size (B)                                       473 345 648
          Gen 2 GC Count (Count / 1 sec)                                 0
          Gen 2 Size (B)                                       213 882 288
          LOH Size (B)                                             245 224
          Monitor Lock Contention Count (Count / 1 sec)                  0
          Number of Active Timers                                        0
          Number of Assemblies Loaded                                  140
          ThreadPool Completed Work Item Count (Count / 1 sec)           0
          ThreadPool Queue Length                                        0
          ThreadPool Thread Count                                        9
          Working Set (MB)                                             787
    
  • Can you tell from this if it appears to be a native leak or a .net memory leak, or a leak on hte loader heap?

    Most of the memory is used by the GC Heap - so this is definitely a managed memory problem

  • Does the memory go down if you wait some time or stay the same?

    Seems to stay pretty stable

  • Looking at the counters for GC counts - do any collections occur after the test is finished? why or why not?

    I have mentioned this a number of times before (like in the GC pop-quiz) but it’s worth mentioning again, garbage collections only occurs on allocations or when you manually call GC.Collect(). After our stress run is done we don’t do neither so no-one will collect the memory until its needed.

Examine task manager

  1. Add the column Working Set in Task Manager
  2. Compare the values for Memory and Working Set to the values for Working Set in the performance counters.

    Note: since they are given in K in task manager you need to multiply these values by 1024 if you want to compare them

  • What does Memory show?

    Around 760 MB

  • What does Working set show?

    Around 720 MB

Here is a description of the different columns in task manager and what they actually show. Note however that most of them show reserved memory - and as we reserve memory in blocks, it doesn’t necessarily equate to the amount of memory used by individual objects, but will give a good indication of how memory grows

  • Working set (memory): The amount of physical memory the process is currently using.
  • Peak working set (memory): The maximum amount of physical memory the process has used.
  • Working set delta (memory): The change in working set memory from the last refresh of the data here.
  • Memory (active private working set): The amount of physical memory used by the process that can’t be used by other processes. Processes frequently cache some data to make better use of your RAM, but can quickly give up that memory space if another process needs it. This column excludes data from suspended UWP processes.
  • Memory (private working set): The amount of physical memory used by the process that can’t be used by other processes. This column does not exclude data from suspended UWP processes.
  • Memory (shared working set): The amount of physical memory used by the process that can be used by other processes when necessary.
  • Commit size: The amount of virtual memory Windows is reserving for the process.

Some things to note about the performance counters

  • Gen 0 heap size shows the budget for Gen 0, not the size of the objects in Gen 0
  • Gen 0, 1, 2 and LOH counters are updated after each collection, not after each allocation
  • Objects over 85000 bytes in size end up on the Large Object Heap
  • The working set consists of the pages loaded in RAM that have been recently touched by the process, it really doesn’t have anything to do with the private bytes of the process (which is what we usually look at when we look at the memory usage of the process), it may be more and it may be less than private bytes. For win forms apps (non-services) there is usually a big difference in the working set when you minimize the apps since the pages are then trimmed from the working set. For services (like ASP.NET that usually doesnt happen so private bytes and working sets tend to stay in the same range)

Get a memory dump

  1. Capture a dump with procdump -ma iisexpress.exe or dotnet-dump -n iisexpress.exe

Analyze the memory dump

  1. Open the dump file in windbg
  2. Set up the symbol path and load sos (see the setup instructions for more info)
  • How big is the dump? Look at the size in the file explorer.

1 048 233 kb

  • How does this compare to the different counters?

It is usually pretty similar to the working set

Examine the .NET GC Heaps

  1. Run !eeheap -gc to examine the size of the .NET GC heaps

     0:000> !eeheap -gc
     Number of GC Heaps: 8
     ------------------------------
     Heap 0 (00000122DDF3FDE0)
     generation 0 starts at 0x00000122E3337880
     generation 1 starts at 0x00000122DF8A4C58
     generation 2 starts at 0x00000122DE3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     00000122DE3A0000  00000122DE3A1000  00000122E3525FE8  0x5184fe8(85479400)
     Large object heap starts at 0x00000126DE3A1000
             segment             begin         allocated              size
     00000126DE3A0000  00000126DE3A1000  00000126DE3B5848  0x14848(84040)
     Heap Size:       Size: 0x5199830 (85563440) bytes.
     ------------------------------
     Heap 1 (00000122DDF66FE0)
     generation 0 starts at 0x000001236344B940
     generation 1 starts at 0x000001235FC80CF8
     generation 2 starts at 0x000001235E3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     000001235E3A0000  000001235E3A1000  0000012363765FE8  0x53c4fe8(87838696)
     Large object heap starts at 0x00000126EE3A1000
             segment             begin         allocated              size
     00000126EE3A0000  00000126EE3A1000  00000126EE3A1018  0x18(24)
     Heap Size:       Size: 0x53c5000 (87838720) bytes.
     ------------------------------
     Heap 2 (00000122DDF6A1D0)
     generation 0 starts at 0x00000123E362A040
     generation 1 starts at 0x00000123DFF99500
     generation 2 starts at 0x00000123DE3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     00000123DE3A0000  00000123DE3A1000  00000123E37841D8  0x53e31d8(87962072)
     Large object heap starts at 0x00000126FE3A1000
             segment             begin         allocated              size
     00000126FE3A0000  00000126FE3A1000  00000126FE3A2050  0x1050(4176)
     Heap Size:       Size: 0x53e4228 (87966248) bytes.
     ------------------------------
     Heap 3 (00000122DDF6D3C0)
     generation 0 starts at 0x0000012463A9E508
     generation 1 starts at 0x00000124600B5790
     generation 2 starts at 0x000001245E3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     000001245E3A0000  000001245E3A1000  0000012463DB5FE8  0x5a14fe8(94457832)
     Large object heap starts at 0x000001270E3A1000
             segment             begin         allocated              size
     000001270E3A0000  000001270E3A1000  000001270E3C1050  0x20050(131152)
     Heap Size:       Size: 0x5a35038 (94588984) bytes.
     ------------------------------
     Heap 4 (00000122DDF705B0)
     generation 0 starts at 0x00000124E3467500
     generation 1 starts at 0x00000124DFC5D720
     generation 2 starts at 0x00000124DE3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     00000124DE3A0000  00000124DE3A1000  00000124E35B19F8  0x52109f8(86051320)
     Large object heap starts at 0x000001271E3A1000
             segment             begin         allocated              size
     000001271E3A0000  000001271E3A1000  000001271E3A74A0  0x64a0(25760)
     Heap Size:       Size: 0x5216e98 (86077080) bytes.
     ------------------------------
     Heap 5 (00000122DDF7B7B0)
     generation 0 starts at 0x00000125637F4A50
     generation 1 starts at 0x000001255FDBE130
     generation 2 starts at 0x000001255E3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     000001255E3A0000  000001255E3A1000  0000012563B41FE8  0x57a0fe8(91885544)
     Large object heap starts at 0x000001272E3A1000
             segment             begin         allocated              size
     000001272E3A0000  000001272E3A1000  000001272E3A1018  0x18(24)
     Heap Size:       Size: 0x57a1000 (91885568) bytes.
     ------------------------------
     Heap 6 (00000122DDF809B0)
     generation 0 starts at 0x00000125E302A0A0
     generation 1 starts at 0x00000125DFB19700
     generation 2 starts at 0x00000125DE3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     00000125DE3A0000  00000125DE3A1000  00000125E3291FE8  0x4ef0fe8(82776040)
     Large object heap starts at 0x000001273E3A1000
             segment             begin         allocated              size
     000001273E3A0000  000001273E3A1000  000001273E3A1018  0x18(24)
     Heap Size:       Size: 0x4ef1000 (82776064) bytes.
     ------------------------------
     Heap 7 (00000122DDF85EE0)
     generation 0 starts at 0x000001266379AD28
     generation 1 starts at 0x000001265FE58080
     generation 2 starts at 0x000001265E3A1000
     ephemeral segment allocation context: none
             segment             begin         allocated              size
     000001265E3A0000  000001265E3A1000  000001266398C2E0  0x55eb2e0(90092256)
     Large object heap starts at 0x000001274E3A1000
             segment             begin         allocated              size
     000001274E3A0000  000001274E3A1000  000001274E3A1018  0x18(24)
     Heap Size:       Size: 0x55eb2f8 (90092280) bytes.
     ------------------------------
     GC Heap Size:    Size: 0x2a20bc20 (706788384) bytes.
    
    • How many heaps do you have? Why?

      I have 8 heaps - one for each processor core. See the GC Pop Quiz for more info

    • How much memory is stored on the .net GC heaps? Compare to GC Heap Size

      690 MB - so exactly as advertised

      0:000> ?0n706788384/0n1024
      Evaluate expression: 690223 = 00000000`000a882f
      
    • How much memory is on the large object heap? Hint: add up the sizes for all the Large Object heap segments. Compare to LOH Size in the perfmon counters.

      ~240 MB

  2. Run !dumpheap -stat to dump out all .net objects in a statistical fashion (Note: you can run !help DumpHeap to get help for the !dumpheap command or check out this post or this post)

     0:000> !dumpheap -stat
     Statistics:
                 MT    Count    TotalSize Class Name
     ...
     00007ffcf7a92aa8     2537       147424 System.SByte[]
     00007ffcf84d5788     7224       173376 System.Diagnostics.Tracing.PollingPayloadType
     00007ffcf7af0db0     1748       181792 System.Reflection.RuntimeMethodInfo
     00007ffcf79d6618      426       187896 System.Object[]
     00007ffcf8253bf8     3081       236120 System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]][]
     00007ffcf79db1f0    11439       274536 System.Int32
     00007ffcf84dd788     4214       370832 System.Diagnostics.Tracing.IncrementingCounterPayload
     00007ffcf84d5cf0     7224       751296 System.Diagnostics.Tracing.CounterPayload
     00007ffcf7a92360      517       801427 System.Byte[]
     00007ffcf81d8378    32008      1024256 BuggyBits.Models.Link
     00007ffcf7a91e18    22676      1258732 System.String
     00007ffcf7c6e470    32368      1553664 System.Text.StringBuilder
     00000122ddea5ce0    50801     54243104      Free
     00007ffcf7ad3058    32428    641275786 System.Char[]
     Total 282813 objects
    
    • Looking at the 5-10 object types that use most memory, what do you think is leaking?

      Most of the memory is used up by Strings and Char[] which is not that unusual to see given how common strings are in any type of app, but ~650 MB definitely seems a bit weird, and 32008 Links (whatever they may be) also sound fishy, especially since there is about the same amount of StringBuilder objects present in the dump.

    • What “size” does the size column show? I.e. what is included in this “size”? Hint: see the post !dumpheap -stat explained for more info.

      If we dump out a Link object with !do we will see that a Link object has a pointer to a StringBuilder (url) and a pointer to a string (name). The size of the link object is shown as 32 bytes which basically just includes the pointers and some overhead (method table etc.)

         0:000> !DumpObj /d 000001266397e508
         Name:        BuggyBits.Models.Link
         MethodTable: 00007ffcf81d8378
         EEClass:     00007ffcf81c8c60
         Size:        32(0x20) bytes
         File:        C:\Tess\source\BuggyBits\src\BuggyBits\bin\Debug\netcoreapp3.1\BuggyBits.dll
         Fields:
                     MT    Field   Offset                 Type VT     Attr            Value Name
         00007ffcf7c6e470  4000005        8 ...ext.StringBuilder  0 instance 000001266397e528 <URL>k__BackingField
         00007ffcf7a91e18  4000006       10        System.String  0 instance 000001255e404dc8 <Title>k__BackingField
      

      If on the other hand you run !objsize on it, you will see that the size shows up as 20168 bytes. This is the size including the member variables, i.e. the size of the Link object and everything it references.

         0:000> !objsize 000001266397e508
         sizeof(000001266397E508) = 20168 (0x4ec8) bytes (BuggyBits.Models.Link)
      

      What you see in !dumpheap is the 32 bytes per link. It wouldn’t be feasible to include the size of the member variables for a few different reasons.

      1. it would take a long time to calculate
      2. some objects (say A and B) may both point to object C, if you did !objsize on A and B they would both include the size for C so the size column would be very confusing since it wouldn’t be exclusive.
      3. in a case like this one with the Link the size for !objsize feels like the correct size since a Link contains a url and a name. However if you look at a web control which has a member var _parent and you run !objsize on the web control, this will include the size of the parent which is not necessarily as obvious.
  3. Dump out stats for various size char[] to find out if there is a pattern (this is a bit of trial and error so you have to try a few different sizes to figure out where the bulk of the strings are.

    Get the method table (MT) for System.Char[] (first column in !dumpheap -stat)

    !dumpheap -mt <char[] MT> -min 0n85000 -stat
    !dumpheap -mt <char[] MT> -min 0n10000 -stat
    !dumpheap -mt <char[] MT> -min 0n20000 -stat
    !dumpheap -mt <char[] MT> -min 0n30000 -stat
    !dumpheap -mt <char[] MT> -min 0n25000 -stat
    
    • In what range (between what sizes) do most of the char[] exist?

      Between 20000 and 25000 bytes

  4. Dump out the strings in that range

     !dumpheap -mt <char[] MT> -min 0n20000 -max 0n25000
    

    In this case most of them will even be the exact same size which is a clue to what is going on

     0:000> !dumpheap -mt 00007ffcf7ad3058 -min 0n20000
             Address               MT     Size
     00000122de50f630 00007ffcf7ad3058    20024
     00000122de514508 00007ffcf7ad3058    20024
     00000122de5193a8 00007ffcf7ad3058    20024
     00000122de51e248 00007ffcf7ad3058    20024
     00000122de5230e8 00007ffcf7ad3058    20024
     00000122de527fe0 00007ffcf7ad3058    20024
     00000122de52ce80 00007ffcf7ad3058    20024
     00000122de531d20 00007ffcf7ad3058    20024
     00000122de540b40 00007ffcf7ad3058    20024
     00000122de545a18 00007ffcf7ad3058    20024
     00000122de54a8b8 00007ffcf7ad3058    20024
     ...
    
  5. Dump out a few of them to find out what they contain

     !do <address of char[] - first column in the !dumpheap -mt output>
    
     0:000> !DumpObj /d 0000012663974830
     Name:        System.Char[]
     MethodTable: 00007ffcf7ad3058
     EEClass:     00007ffcf7ad2fd8
     Size:        20024(0x4e38) bytes
     Array:       Rank 1, Number of elements 10000, Type Char (Print Array)
     Content:     http://blogs.msdn.com/tom.......................................................................................................
     Fields:
     None
    
    • What do these char[] contain?

      Link URLs

  6. Pick a few and find out where they are rooted (i.e. why they can’t be collected) Note: You may want to try a couple different ones.

     !gcroot <address of char[]>
    
     0:000> !gcroot 00000122de50f630
     Finalizer Queue:
         00000122DE50F5C8
         -> 00000122DE50F5C8 BuggyBits.Models.Link
         -> 00000122DE50F5E8 System.Text.StringBuilder
         -> 00000122DE50F630 System.Char[]
    
     Found 1 unique roots (run '!gcroot -all' to see all roots).
    
    • Where are they rooted? Why?

      The Char[] is a member of a StringBuilder which in turn is a member of a Link object, which is rooted in the Finalizzer Queue so it is ready for finalization, just waiting for the finalizer to call its destructor so it can be garbage collected.

Examine the finalizer queue and the finalizer thread

  1. Look at the finalizer queue

     !finalizequeue
    
     ```cmd
     0:000> !finalizequeue
     SyncBlocks to be cleaned up: 0
     Free-Threaded Interfaces to be released: 0
     MTA Interfaces to be released: 0
     STA Interfaces to be released: 0
     ----------------------------------
     ------------------------------
     Heap 0
     generation 0 has 80 finalizable objects (000001276A6A8228->000001276A6A84A8)
     generation 1 has 5 finalizable objects (000001276A6A8200->000001276A6A8228)
     generation 2 has 34 finalizable objects (000001276A6A80F0->000001276A6A8200)
     Ready for finalization 3660 objects (000001276A6A84A8->000001276A6AF708)
     ------------------------------
     Heap 1
     generation 0 has 85 finalizable objects (000001276A7034D0->000001276A703778)
     generation 1 has 24 finalizable objects (000001276A703410->000001276A7034D0)
     generation 2 has 2 finalizable objects (000001276A703400->000001276A703410)
     Ready for finalization 3845 objects (000001276A703778->000001276A70AFA0)
     ------------------------------
     Heap 2
     generation 0 has 56 finalizable objects (000001276A6E6EE0->000001276A6E70A0)
     generation 1 has 11 finalizable objects (000001276A6E6E88->000001276A6E6EE0)
     generation 2 has 3 finalizable objects (000001276A6E6E70->000001276A6E6E88)
     Ready for finalization 3971 objects (000001276A6E70A0->000001276A6EECB8)
     ...
     Statistics for all finalizable objects (including all objects ready for finalization):
                 MT    Count    TotalSize Class Name
     ...
     00007ffcf80cee28       16          896 System.Threading.ThreadPoolWorkQueueThreadLocals
     00007ffcf7a95650       21         1512 System.Threading.Thread
     00007ffcf80337d0       31         1736 System.Buffers.MemoryPoolBlock
     00007ffcf7ab4ab8       20         2240 System.Diagnostics.Tracing.EventSource+OverideEventProvider
     00007ffcf80be4e0       76         5472 System.Reflection.Emit.DynamicResolver
     00007ffcf81d8378    31881      1020192 BuggyBits.Models.Link
     Total 32123 objects
     ```
    
    • What objects are listed in the !finalizequeue output? Hint: run !help finalizequeue

      All objects that have finalizers/destructors are registered with the finalizer queue so that the finalizer will run the destructor once the object is garbage collected unless finalization is supressed in the dispose method.

    • How many objects are “ready for finalization”? What does this mean?

      32123 objects. So these objects are garbage collected and ready to be finalized. If ready for finalization shows anything above 0 there is a pretty good chance the finalizer is blocked which is why these objects stick around waiting to be finalized, and with them goes all the memory they reference.

  2. Find the finalizer thread to determine what it is doing. Run !threads and look for the thread listed with (Finalizer)

  3. Move to the finalizer thread and examine the managed and native call stack

     ~5s                   # 5 should be substituted with the id of the finalizer thread
     kb 2000
     !clrstack
    
     0:000> !threads
     ThreadCount:      37
     UnstartedThread:  0
     BackgroundThread: 20
     PendingThread:    0
     DeadThread:       16
     Hosted Runtime:   no
                                                                                                             Lock
     DBG   ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
     21    1 7e54 00000122DDA4E4A0  202a020 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     MTA
     31    2 4260 00000122DDDEAD00  202b220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     MTA (Finalizer)
     33    3 87e8 00000122DDDEF450  102a220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     MTA (Threadpool Worker)
     XXXX    4    0 0000012769F3A660  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     34    6 55f0 0000012769F4A780  8029220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     MTA (Threadpool Completion Port)
     35    7 4498 000001276A038F00  202b220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     MTA
     XXXX    8    0 000001276A3BD8F0  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX    9    0 000001276A042910  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     9   10 1a0c 000001276A424310    20220 Preemptive  00000124E35AFA38:00000124E35B19E0 00000122dde5c0d0 0     Ukn
     XXXX   11    0 0000012769F37F70  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   12    0 000001276A4F1C70  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   13    0 000001276A4F22A0  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   14    0 000001276A4F28D0  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     32   15 2834 000001276A3C9F30    20220 Preemptive  00000123E3782380:00000123E37841C0 00000122dde5c0d0 0     Ukn
     37   16 5334 000001276A4F1540  202b220 Preemptive  0000012363765648:0000012363765FD0 00000122dde5c0d0 0     MTA
     XXXX   17    0 000001276A5DD070  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   19    0 000001276A5DAA90  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     5   18 67e0 000001276A5DD6C0    20220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     6   20 8164 000001276A5DC3D0    20220 Preemptive  00000123E3781D98:00000123E37821C0 00000122dde5c0d0 0     Ukn
     8   21  c3c 000001276A5DB730    20220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     XXXX   22    0 000001276A5DCA20  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   23    0 000001276A5DDD10  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   24    0 000001276A5DE360  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   25    0 000001276A5DB0E0  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   26    0 000001276A5DBD80  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   27    0 000001276A57F6E0  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     XXXX   28    0 000001276A57BE10  1039820 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn (Threadpool Worker)
     7   29 70d8 000001276A57C460    20220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     4   30 6d90 000001276A57DDA0    20220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     39   31 8490 000001276A57CAB0    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     40   32 8644 000001276A582310    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     41   33 71d8 000001276A57FD30    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     42   34 626c 000001276A5809D0    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     43   35 465c 000001276A580380    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     44   36 87b0 000001276A57D100    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     45   37 63a0 000001276A57EA40    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     46   38 8474 000001276A57D750    21220 Preemptive  0000000000000000:0000000000000000 00000122dde5c0d0 0     Ukn
     0:000> ~31s
     ntdll!NtDelayExecution+0x14:
     00007ffd`ea54c634 c3              ret
     0:031> !clrstack
     OS Thread Id: 0x4260 (31)
             Child SP               IP Call Site
     00000019B1CFF7A8 00007ffdea54c634 [HelperMethodFrame: 00000019b1cff7a8] System.Threading.Thread.SleepInternal(Int32)
     00000019B1CFF8A0 00007ffcf83d805c BuggyBits.Models.Link.Finalize() [C:\Tess\source\BuggyBits\src\BuggyBits\Models\Link.cs @ 20]
     00000019B1CFFBD0 00007ffd57526b16 [DebuggerU2MCatchHandlerFrame: 00000019b1cffbd0]
    
    • What object is it finalizing?

      Looks like it is cleaning up a Link object

    • What is it doing? Why is this causing high memory usage?

      The finalizer for the Link object is stuck in a sleep which means that the finalizer is blocked so nothing in the process can be finalized, thus all the objects waiting for finalization will continue to stay in memory until the finalizer is unblocked and they can be finalized.

Examine the code for verification

  1. Open Link.cs to find the destructor/finalizer for the Link class to see the problematic code

Performing any type of blocking operations in the finalizer is very dangerous since this causes no objects to be finalized. Apart from checking that you do not perform any blocking operations in the finalizer you should always strive to dispose/close all objects with finalizers/destructors so that you don’t end up in this all too common situation.

Hope you found this useful… the next lab will be a high CPU in GC lab…

Laters, Tess