ANTS Memory Profiler 7

Memory management primer

This page is an overview of .NET memory management. It should give you enough information about how .NET allocates objects into memory to start using ANTS Memory Profiler.

Large and small objects

.NET handles large objects and small objects differently:

  • Large objects are all objects over 85k, and double (multidimensional) arrays over 8k.
  • All other objects are considered small objects.

The small object heaps

Small objects are stored in the small object heaps.

Objects are allocated first to the generation 0 (Gen 0) heap.

When the Gen 0 heap is full, the .NET garbage collector runs. The garbage collector performs two tasks:

  1. It disposes of all objects on the Gen 0 heap that are no longer needed.
  2. It moves objects that are still needed to the generation 1 (Gen 1) heap.

This leaves the Gen 0 heap empty, ready for new objects to be allocated on it.

Eventually, the Gen 1 heap becomes full. When this happens, the garbage collector runs again, and it performs three tasks:

  1. It disposes of all objects on the Gen 1 heap that are no longer needed.
  2. It moves objects that are still needed to the generation 2 (Gen 2) heap.
  3. It performs a garbage collection on Gen 0, as before.

If Gen 2 becomes full, a full garbage collection takes place. When this happens, the garbage collector performs four tasks:

  1. It disposes of all objects on the Gen 2 heap that are no longer needed.
  2. It performs a garbage collection on Gen 1, as before.
  3. It performs a garbage collection on Gen 0, as before.
  4. It disposes of objects on the large object heap that are no longer needed (see below).

If insufficient memory has been freed at the end of a full garbage collection, an OutOfMemory exception is thrown.

Note that whenever the garbage collector runs on any of the small object heaps, the heaps are compacted, so that the available space is used efficiently.

By using this generational approach, recently-allocated objects should be disposed sooner than older objects, which are presumed to be being kept in memory for a reason. It also ensures that full garbage collections, which have the greatest impact on an application's performance, happen as infrequently as possible.

ANTS Memory Profiler forces a garbage collection when you take a snapshot. This is because some of the memory counters that ANTS Memory Profiler obtains from Windows are only accurate immediately after a garbage collection. For this reason, you will generally not see any objects on the Gen 0 heap during profiling. This behavior will not affect the results that you obtain, because an object which only exists for a short time on Gen 0, and which is never promoted to Gen 1, will never be the cause of a .NET memory problem.

(If you do see objects on the Gen 0 heap, it is because the .NET garbage collector does not always behave in the way described in this simplified overview. For example, pinned objects, objects on the finalizer queue and objects created during the garbage collection might remain on the Gen 0 heap after a snapshot. Again, this is never a problem that would affect the results obtained.)

The large object heap

The large object heap is where large objects are stored.

There is only one large object heap, and objects stored on it are only tidied-up during a full garbage collection.

Importantly, because compacting the large object heap would adversely affect your application's performance, the large object heap is never compacted. This means that the heap can fragment over time. If a new object, which is bigger than an existing fragment, needs to be allocated on the heap, the large object heap will grow so that the new object can be allocated to the end of the heap. Eventually, the fragmentation may mean that there is insufficient memory to allocate new large objects.

This is often a major cause of .NET memory problems, and it can be one of the most difficult issues to solve. For more information, see how to check for large object heap fragmentation.

Further information on large object heap fragmentation

Managed memory leaks

The garbage collector calculates whether or not an object can be disposed from one of the heaps by creating a graph (similar to the Instance Retention Graph in ANTS Memory Profiler), which shows how the object is referenced by other objects.

The graph starts from the GC roots. A GC root can be any storage slot to which the running program has access, such as a local variable, static variables, or even a CPU register. (Strictly speaking, the object itself is not the GC root; the storage slot that holds the reference to the object is the GC root.)

The GC roots will reference objects, which in turn typically reference other objects (through a member variable). If an object is not being referenced by anything, it cannot be reached by your program, and will be removed by the garbage collector.

In the graph above, System.String is referenced by:

  1. The key of System.Collections.Hashtable+bucket[2].
    1. System.Collections.Hashtable+bucket[] is referenced by the buckets property of System.Collections.Hashtable.
    2. System.Collections.Hashtable is a GC root because it is referenced by the static variable SharedPerformanceCounter.categoryDataTable.
  2. The categoryName property of System.Diagnostics.SharedPerformanceCounter.
    1. System.Diagnostics.SharedPerformanceCounter is referenced by the sharedCounter property of System.Diagnostics.PerformanceCounter. (The green background means that System.Diagnostics.PerformanceCounter is disposable, but not yet disposed.)
    2. System.Diagnostics.PerformanceCounter is referenced by the _instance property of System.Data.ProviderBase.DbConnectionPoolCounters+Counter.
    3. System.Data.ProviderBase.DbConnectionPoolCounters+Counter is referenced by the HardConnectsPerSecond property of the base class DbConnectionPoolCounters in System.Data.SqlClient.SqlPerformanceCounters.
    4. System.Data.SqlClient.SqlPerformanceCounters is a GC root because it is referenced by the static variable SqlPerformanceCounters.SingletonInstance.

Because references hold objects in memory, you have to be careful to ensure that references to objects are removed when no longer needed. A common mistake, for example, is to leave an event handler (such as a timer) referencing an object. This will stop the garbage collector from disposing the object. The result is that an object which should have been in memory only briefly can end up leaked onto the Gen 2 heap. Importantly, in this scenario, all of the objects that the object references will also be on the Gen 2 heap. This has significant implications on how you use ANTS Memory Profiler: the objects referenced by the leaked object will often be more obvious than the leaked object itself, so you need to start by investigating those referenced objects (which are most commonly strings or byte arrays).

The typical symptom of a memory leak is that the performance degrades while the program runs but the amount of memory used recovers on restart and then degrades again.

See finding and fixing a memory leak.

Further information on finding leaks in managed code

Unmanaged memory leaks

Managed .NET code may interoperate with unmanaged code when some COM objects or native-code DLLs are invoked.

Leaks caused by unmanaged objects are more complex to identify, because ANTS Memory Profiler cannot provide detailed information about unmanaged usage. By understanding and interpreting the data shown by ANTS Memory Profiler, however, you might gain some ideas about where to look for these leaks.

The main indication of an unmanaged memory leak is when the number of private bytes (the amount of real and paged memory requested by the program) increases while the number of bytes in the .NET heap does not grow as quickly. You may be able to find the source of the problem by looking for .NET objects whose instance count is increasing, or by looking for objects on the finalizer queue that have not been disposed.

See checking for unmanaged memory usage.

Further information on finding leaks in unmanaged code


This page should have given you enough knowledge to start using ANTS Memory Profiler. If you are interested in learning more detail than this brief overview can provide, see our webinar 5 Misconceptions about .NET Memory Management.

Didn't find what you were looking for?