dotMemory 2018.2 Help

Analyzing GC Roots

Retention path of an object always starts with a GC root. From the point of Garbage Collector, root is a reference to an object that must not and will not be collected. This makes roots the only possible starting point for building retention graphs. Understanding root types can be extremely important during the "Who retains the object?" analysis. Sometimes examining retention paths does not give you an answer why the object is still in memory. In this case, it makes sense to look at GC roots. For example, a RefCounted handle gives you a clue that the object is retained by some unmanaged COM library.

There are four possible root types in .NET Framework:

  • Stack references: references to local objects. Such roots live during a method execution.
  • Static references: references to static objects. These roots live the entire app domain life time.
  • Handles: typically, these are references used for communication between managed and unmanaged code. Such roots must live at least until the unmanaged code needs "managed" objects.
  • Finalizer references: references to objects waiting to be finalized. These roots live until the finalizer is run.

All dotMemory views used to analyze objects' retention paths (Group by Similar Retention, Group by Shortest Paths, Key Retention Paths, and Shortest Paths to Roots) also show GC roots. Root types distinguished by dotMemory are described below.* above).

Regular local variable

This is a local variable declared in a method (variable on the stack). Reference to this variable becomes a root during the method life time. For example:

static void Main() { ... var collection = new Collection <int>(); ... }
regular local variable

Note that in release builds, root's life time may be shorter - JIT can discard the variable right after it is no longer needed.

Static reference

When CLR meets static object (class member, variable, or event), it creates a global instance of this object. The object can be accessed during the entire app life time, so static objects are almost never collected. Thus, references to static objects is one of the main root types.

class StaticClass { public static Collection<string> StCollection; }

After the collection is initialized, CLR will create a static instance of the collection. The reference to the instance will exist during the application domain life time.

static reference 1

When the static object is referenced through a field, dotMemory shows you field's name.

Of course, "unnamed" static references can also take place. The most obvious example of such a root is a reference to a string declared in a method.

static void Main() { ... string A = "This is a string"; ... }
static reference 2

Note that in the example above, the Regular local variable reference is also created by CLR. But, to simplify further analysis, dotMemory doesn't show you this root.

F-reachable queue / Finalization queue

CLR provides a helpful mechanism for releasing unmanaged resources: the finalization pattern. The System.Object type declares a virtual method Finalize (also called the finalizer) that is called by the Garbage Collector before the object's memory is reclaimed. Typically, you override this method in order to release unmanaged resources. Any object that has a finalizer is put to the Finalization queue (in dotMemory these objects have Finalization Queue root). When a garbage collection takes place, the GC finds such object in the Finalization queue but doesn't run its finalizer directly. Instead, the GC puts the object to the F-reachable queue (the F-Reachable Queue root in dotMemory) and runs the finalizer in a separate Finalization thread (all of these is done for the sake of performance as the finalizer can potentially run any amount of code). On the next GC, the object in the F-reachable queue is garbage collected. The described pattern has drawbacks, and that's why dotMemory offers a special Finalizable objects inspection.

Note that due to the nature of memory profiling, dotMemory always runs a full GC before a snapshot is taken. That's why you won't find objects with the Finalization Queue root in snapshots taken via dotMemory. This root type is possible only in raw memory dumps.

Pinning handle

Interaction of managed and unmanaged code is an additional problem for the Garbage Collector. For example, you need to pass an object from the managed heap to, say, an external API library. As a small object heap is compacted during collection, the object can be moved. This is a problem for the unmanaged code if it relies on the exact object location. One of the solutions is to fix the object in the heap. In this case, GC gets a pinning handle to the object which implies that the object cannot be moved. Thus, if you see the Pinning handle type, then probably the object is retained by some unmanaged code. For example, the App object always has a pinned reference.

pinning handle

Note: You can also pin objects intentionally using the fixed block.

RefCounted handle

The root prevents garbage collection if the reference count of the object is a certain value. If an object is passed to a COM library using COM Interop, CLR creates a RefCounted handle to this object. This root is needed as COM is unable to perform garbage collection. Instead, it uses reference counting. If the object is no longer needed, COM sets the count to 0. This means that RefCounted handle is no longer a root and the object can be collected.

Thus, if you see RefCounted handle, then, probably, the object is passed as an argument to unmanaged code.

refcounted handle

Weak handle

As opposed to other roots, the Weak handle does not prevent referenced objects from garbage collection. Thus, objects can be collected at any time but still can be accessed by the application. Access to such objects is performed via an intermediate object of the WeakReference type. Such an approach can be very efficient when working with temporary data structures like cache. As weak references do not survive full garbage collection, the weak reference handle can come only in combination with other handles. For example, Weak, RefCounted handle.

weak handle

Regular handle

When handle type is undefined, dotMemory marks it as Regular handle. Typically, these are references to system objects required during the entire app life time. For example, the OutOfMemoryException object. To prevent its collection, the environment references the object through a regular handle.

regular handle
Last modified: 4 September 2018