#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)));

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

            GC.Collect();
            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.
938-001

#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.

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

#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.

#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);
            myDog.Bark();

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

            // 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.

#740 – Short vs. Long Weak References

You can create a WeakReference object that refers to a regular reference-typed object and lets you discover whether the original object has been garbage collected or not.  You can use the Target property of the WeakReference to reference the original object, if it’s still alive.

By default, a WeakReference object creates a short weak reference–the target of the weak reference becomes null and the IsAlive property reports false as soon as the object’s finalizer is called, but potentially before the object is actually garbage collected.

You can also create a long weak reference by passing a value of true as the second parameter to the WeakReference constructor.  The Target will remain non-null after the object has been finalized, up until the point in time when it actually gets garbage collected.  In this way, you retain access to an object that has been finalized, but not collected.

#739 – Avoid Accessing an Object After Its Been Finalized

A finalizer should not create a new reference to the object being finalized.  Below is an example where doing this leads to the ability to reference the object after it’s been finalized.

This is an example of questionable code–in general, it’s dangerous to reconstitute an object after it’s been finalized.

In the Dog class, we save a reference to the object being finalized.

    public class Dog
    {
        public static Dog KeepDogRef;

        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        ~Dog()
        {
            Console.WriteLine("Dog destructor for " + Name + " called");
            Dog.KeepDogRef = this;
        }

        public void Bark()
        {
            Console.WriteLine(Name + " : Woof");
        }
    }

In main program, we reconstitute a Dog object after it’s been finalized.

            Dog dog = new Dog("Bowser");

            WeakReference dogRef = new WeakReference(dog);

            // Unref Bowser
            dog = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();

            // Bowser is gone
            Console.WriteLine(string.Format("Object still alive: {0}", dogRef.IsAlive));

            // Hey, Bowser is alive again!
            Dog newRef = Dog.KeepDogRef;
            newRef.Bark();

739-001