Hi all,

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

 

MANAGED HEAP. Part 4

 

·         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: Number of 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

Statistics:

      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             

001c13fc             

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.

 

Regards,

 

Alex (Alejandro Campos Magencio)