#939 – Not All Objects on Heap Are Promoted to Next GC Generation

Objects on the managed heap are grouped into generations by the garbage collector (GC), as follows:

  • Generation 0 – Objects that have been created since the last GC pass  (newest objects)
  • Generation 1 – Objects that have survived one pass of the GC
  • Generation 2 – All other objects  (oldest objects)

Objects are only promoted to the next generation if the generation that they are currently located in is examined and collected during a garbage collection pass.

This means:

  • Since the GC always examines Gen 0 during any GC pass, Gen 0 objects that survive a garbage collection are always promoted to Gen 1.
  • Objects in Gen 1 are only promoted to Gen 2 if Gen 1 is examined and collected during a GC pass and the object survives.  The GC will very often do only a Gen 0 pass during collection.  When the GC only examines and collects Gen 0, Gen 1 objects are not examined and therefore not promoted to Gen 2.

#938 – Finding Out What GC Generation an Object Is In

The garbage collector (GC) groups objects into generations to avoid having to examine and collect all objects in memory whenever a garbage collection is done.

For debugging purposes, it’s sometimes useful to know which generation an object currently belongs to.  You can get this information using the GC.GetGeneration method, as shown below.

            Dog bob = new Dog("Bob", 5);
            Console.WriteLine(string.Format("Bob is in generation {0}", GC.GetGeneration(bob)));

            Console.WriteLine(string.Format("Bob is in generation {0}", GC.GetGeneration(bob)));

            Console.WriteLine(string.Format("Bob is in generation {0}", GC.GetGeneration(bob)));

In the example above, the “Bob” Dog object starts out in generation 0. After we do the first garbage collection, it’s promoted to generation 1 and after the 2nd collection, it’s promoted to generation 2.

#937 – Forcing a Garbage Collection

The garbage collector (GC) normally runs automatically, doing a garbage collection pass when necessary (when there is memory pressure and you are allocating memory for some new object).

You typically just let the garbage collector run automatically, never explicitly asking it to do garbage collection.

For testing purposes, however, you might want to force garbage collection to happen at a particular time.  You can do this by calling the GC.Collect method.  Calling Collect will force a collection across all generations.  You can also specify the highest generation to collect as follows:

  • GC.Collect() – Collect generations 0, 1, 2
  • GC.Collect(0) – Collect generation 0 only
  • GC.Collect(1) – Collect generations 0, 1

Objects with finalizers will not be collected when you call Collect, but rather placed on the finalization queue.  If you want to also release memory for these objects, you need to wait until their finalizers are called and then do another garbage collection pass.


#936 – Visualizing Garbage Collection Generations

The garbage collection groups objects on the managed heap into generations.  This improves the performance of the garbage collector (GC), since it typically collects objects in Generation 0 (newest), only moving to older generations if necessary.

Below is an example of this.  To start with, we have an empty managed heap (no objects).  The entire heap is considered Generation 0, since we haven’t yet done a garbage collection.


We now allocate three Dog objects on the heap.  They are allocated in the first available space within Gen 0.


We now set the yourDog reference to null, so that Jack is no longer referenced.  Before garbage collection, the heap looks like this:


Assume that a GC pass runs now and collects objects in Gen 0 (the only generation available).  Memory for Jack is released, everything in Gen 0 is compacted, and whatever remains is marked as Gen 1.  Gen 0 now starts again at the free space pointer.


Assume that we run for a while and allocate a couple more Dog objects–Lassie and Rin Tin Tin.  Then assume that the reference to Lassie is removed and that the reference to Ruby is also removed.  Before collection, the heap looks as follows.  Notice that there are unreachable objects in both Gen 0 and Gen 1.


Assume that a garbage collection now runs and collects only Gen 0.  Memory for Lassie is reclaimed, Gen 0 is compacted and and its objects are promoted to Gen 1.  The existing Gen 1, however, is not collected and its objects remain in Gen 1.


Finally, let’s assume that the garbage collection runs one more time.  It begins with Gen 0, but there is nothing to collect.  Let’s assume that there are high memory demands that cause the GC to decide to also collect Gen 1.  It can now release memory for Ruby and then compacts Gen 1 and promotes its surviving objects to Gen 2.


#935 – Large Objects Are Allocated on the Large Object Heap

The managed heap is the area in memory where reference-typed objects are allocated.  When you create a new object, a portion of the managed heap is allocated for the object.

In reality, objects are stored on either the Small Object Heap (SOH) or the Large Object Heap (LOH).  Objects that are larger than 85,000 bytes are allocated on the LOH.  All other objects are allocated on the SOH.

Other differences between the two heaps:

  • The SOH is generational–objects belong to generation 0, 1, or 2.  The LOH is not sub-divided into generations
  • The LOH is only garbage collected when generation 2 of the SOH is collected.  (I.e. Rarely)
  • After the LOH is garbage collected, the heap is not compacted.  This results in the memory becoming fragmented and requires maintaining a linked list of free blocks.  (The SOH is compacted after every collection).
  • Allocation on the LOH can be slower than the SOH, due to the fragmentation.

#934 – How Generations Help the Garbage Collector Run More Efficiently

Objects allocated on the managed heap are grouped into generations by the garbage collector:

  • Generation 0 – Newest objects
  • Generation 1 – Objects that have survived one GC pass
  • Generation 2 – Objects that have survived more than one GC pass

When the garbage collector performs a GC pass, it looks only at objects in generation 0, releasing memory for any that are no longer reachable.  In this way, the garbage collector is more efficient, because it is only looking at a portion of the managed heap.

If the garbage collector has collected Gen 0, but the application requires more memory, the garbage collector can then garbage collect generation 1.  If the application still requires memory after collecting generation 1, it can move on to generation 2.

This scheme relies on the fact that if an object survives one GC pass, it likely has a longer lifetime and will survive future passes.

#933 – The Garbage Collector Groups Objects into Generations

Referenced-typed objects in an application have different lifetimes.  The application will use some objects as long as the application is running.  Others are referenced only during execution of a single method.

If the garbage collector always examined every object whenever it did a garbage collection pass, it would spend a lot of time reexamining longer-living objects that can’t yet be garbage collected.  The garbage collector can perform more efficiently by looking at only a subset of all objects during each pass.  It does this by grouping objects into generations:

  • Generation 0 – Objects that have been created since the last GC pass  (newest objects)
  • Generation 1 – Objects that have survived one pass of the GC
  • Generation 2 – All other objects  (oldest objects)

The garbage collector examines and collect objects in generation 0, moving to higher generations only if it needs additional memory.

Objects are promoted to the next generation only if a GC pass is done on the generation in which they are located.

#932 – When Objects Become Eligible for Garbage Collection

In general an object can be garbage collected when it is no longer referenced by any other object or reference-typed variable.  For example, the Dog object named “Kirby” is no longer referenced in the code snippet below when the myDog variable is set to null.

            Dog myDog = new Dog("Kirby", 15);

            Dog yourDog = new Dog("Ruby", 3);

            // Kirby no longer referenced after this line executes
            myDog = null;

In practice, however, an object may become eligible for collection earlier than you think.  In the code sample above, the “Kirby” object will not be referenced at any time after the first call to the Bark method.  This means that the compiler may decide to garbage collect the “Kirby” object after the call to this method, i.e. before myDog is actually set to null.


#931 – Objects with Finalizers Take Longer to Garbage Collect

The purpose of the garbage collector is to discover objects on the managed heap that are no longer reachable and to reclaim the memory occupied by these objects.

Once an object is discovered to be no longer reachable, the garbage collector checks to see if the object has a finalizer.  If the object doesn’t have a finalizer, it is marked for collection and its memory is reclaimed by the garbage collector.

If an object has a finalizer, it will not be marked for garbage collection, but instead be marked for finalization.  It will not get garbage collected during the current pass of the garbage collector.  Instead, its finalizer will be run at some point after garbage collection finishes.  It will then be eligible for collection during the next pass of the garbage collector.

Reclaiming memory for objects with finalizers therefore requires at least two passes of the garbage collector.

#930 – Objects on the Heap Can Refer to Each Other

When you set a reference-typed variable to refer to an object, either by creating a new object, or by referencing an existing object, you reference an object on the heap.  As long as the reference exists, the object will be allowed to exist on the managed heap and not get garbage collected.

In addition to an object being referenced by a reference-typed variable, objects may be referenced by other objects.  It may also be the case that an object is only referenced by another object and not by any reference-typed variable.  These objects will be kept alive only if they can be reached by some reference.

In the example below, the dogs HankLassie and Rex will all become eligible for collection because they are not reachable by any referenced-typed variable.  Dogs Kirby and Jack will be retained, because they are reachable.