MANAGED DEBUGGING with WINDBG. Managed Heap. Part 4

Hi all,

This post is a continuation of MANAGED DEBUGGING with WINDBG. Managed Heap. Part 3.


· We can troubleshoot Heap issues:

These are some of the important performance counters we can check with Performance Monitor when having memory issues with .NET applications:

§ Process:Private Bytes: All the memory that the app is using (committed, could be paged). When this value reaches Virtual Bytes, Virtual Bytes doubles its size. If memory is shared, it won’t count as Private Bytes, but as Working Set.

§ Process:Virtual Bytes: Virtual Memory of a process. Memory can be reserved or used (committed, reflected as Private Bytes). This counter is useful when we get Out of Memory exceptions.

§ Process:Working Set: How much memory is paged in RAM.

§ .NET CLR Memory:#bytes in all heaps: .NET won’t use the NT Heap, but saves some virtual memory for its own heap. If this value doesn’t grow but Virtual Bytes do, the memory leak is in the unmanaged world.

§ .NET CLR Memory:#gen 0/1/2 collections.

§ .NET CLR Memory:Gen 0/1/2 Heap Size.

§ .NET CLR Memory:Large Object Heap Size.

§ .NET CLR Memory:# induced GC: Number of calls to GC.Collect. Calling this function in our code is not recommended, as GC auto-adjust for better performance.

§ .NET CLR Memory:# of pinned objects: Numberof objects which reference has been blocked (“pinned”) in memory. GC won’t be able to move pinned objects around memory to optimize available space in heap.

§ .NET CLR Memory:finalization survivors: Number of objects which survived a GC because of having a Finalizer method.

§ .NET CLR Memory:Promoted Finalization Memory from Gen 0/1.

§ .NET CLR Memory:Promoted Memory from Gen 0/1.

§ .NET CLR Memory:% Time in GC.

§ .NET CLR Loading:Bytes in Loader Heap: A continuous increase in the size of the Loader Heap is typically an effect of extensive usage of dynamic modules (.NET CLR Loading:Current Assemblies) or having “debug=true” in ASP.NET apps.

§ .NET CLR Loading:Current Assemblies.

§ .NET CLR Loading:Current appdomains.

Additionally, we can generate a log file which can be used to troubleshoot managed memory usage issues. We can take a look to this log file with CLR Profiler :

0:004> !TraverseHeap c:\memory.log

Writing CLRProfiler format to file c:\memory.log

Gathering types...

tracing roots...

Scan Thread 0 OSTHread 1440

Scan Thread 2 OSTHread 194

Walking heap...


file c:\memory.log saved

We can also check the heap for signs of corruption:

0:004> !VerifyHeap

-verify will only produce output if there are errors in the heap

· We can troubleshoot GCHandle leaks:

Sometimes the source of a memory leak is a GCHandle leak. If we discard a GCHandle which points to i.e. a big array without freeing it, the array will be kept alive.

We can see statistics about GCHandles in the process:

0:004> !GCHandles

GC Handle Statistics:

Strong Handles: 33

Pinned Handles: 8

Async Pinned Handles: 0

Ref Count Handles: 0

Weak Long Handles: 56

Weak Short Handles: 384

Other Handles: 0


MT Count TotalSize Class Name

790fd0f0 1 12 System.Object


7b485894 357 22848 System.Windows.Forms.Internal.DeviceContext

7912d8f8 8 28032 System.Object[]

Total 481 objects

We can also try to track down GCHandle leaks. We can search all of memory for any references to the Strong and Pinned GCHandles in the process:

0:004> !GCHandleLeaks


GCHandleLeaks will report any GCHandles that couldn't be found in memory.

Strong and Pinned GCHandles are reported at this time. You can safely abort the

memory scan with Control-C or Control-Break.


Found 41 handles:

001c1118 001c1120 001c1124 001c1148


001c13ec 001c13f0 001c13f4 001c13f8


Searching memory

Reference found in stress log will be ignored

Found 001c1188 at location 0030f23c

Found 001c11fc at location 00424fb8


Found 001c11d4 at location 7a3b9c88


All handles found.

A leak may still exist because in a general scan of process memory SOS can't

differentiate between garbage and valid structures, so you may have false

positives. If you still suspect a leak, use this function over time to

identify a possible trend.


Note that if a handle is found, we’ll see the address of the reference. This might be a stack address or a field within an object, for example.

We can see the object a handle references by dereferencing it:

0:004> !do poi(001c1118)

Name: WindowsApplication1.Form1


If a handle is not found in memory, we get a notification:

0:004> !GCHandleLeaks


GCHandleLeaks will report any GCHandles that couldn't be found in memory.

Strong and Pinned GCHandles are reported at this time. You can safely abort the

memory scan with Control-C or Control-Break.


Found 249 handles:

01dc1200 01dc1208 01dc1210 01dc1218


Searching memory

Found 01dc1ff8 at location 0012ce48



Some handles were not found. If the number of not-found handles grows over the

lifetime of your application, you may have a GCHandle leak. This will cause

the GC Heap to grow larger as objects are being kept alive, referenced only

by the orphaned handle. If the number doesn't grow over time, note that there

may be some noise in this output, as an unmanaged application may be storing

the handle in a non-standard way, perhaps with some bits flipped. The memory

scan wouldn't be able to find those.


Didn't find 232 handles:

01dc1200 01dc1208 01dc1210 01dc1218


0:004> !do poi(01dc1218)


If a serious leak is going on, we’ll get an ever-growing set of handle addresses that couldn't be found.

Next post: MANAGED DEBUGGING with WINDBG. Managed Heap. Part 5.

Index: MANAGED DEBUGGING with WINDBG. Introduction and Index.


Alex (Alejandro Campos Magencio)