To Null or Not to Null


GC Myth: setting an object’s reference to null will force the GC to collect it right away.
GC Truth: setting an object’s reference to null will sometimes allow the GC to collect it sooner.

As much as you may want to, you can’t guarantee the GC will collect what you want, when you want it to. The best you can do is give it hints. The GC typically does collections when an allocation fails, not necessarily as soon as you’re done with an object.

Consider the following method. What is the earliest point the GC could collect the Object that obj references?

void Foo() {
    Object obj = new Object();
    Console.WriteLine(obj.ToString());
    for (int i=0; i<10; i++) {
        Console.WriteLine(i);
    }
    obj = null;
}

The answer depends on whether you’ve compiled it in Debug or Release mode. In Debug, the JIT extends all references’ lifetimes to the end of their scope. This is so you don’t have to worry about the GC cleaning up a variable you’re trying to inspect!

In Release mode, the Object is actually first eligible for collection after the call to ToString(). The JIT is usually smart enough to realize that obj = null can be optimized away. That leaves obj.ToString() as the last reference to the object. So after the call to ToString(), the obj is no longer used and is removed as a GC Root. That leaves the Object in memory orphaned (no references pointing to it), making it eligible for collection. When the GC actually goes about collecting it, is another matter.

So when does setting a reference to null allow the object in memory to be reclaimed sooner?

Consider instance variables. Imagine a class that contains a large array of a managed type. There could be times where you no longer need that array, but you want the object to hang around. This is a good case for the Dispose pattern:

class BigObject : IDisposable {
    int[,] array = new int[1024,1024];
    public void Dispose() {
        array = null;
    }
}

Now the GC will clean up the array when it does its next collection, and you can keep that BigObject around for whatever reason, and not worry about it taking up so much memory.

Comments (13)

  1. Joe says:

    I don’t think that’s a good example for IDisposable. IDisposable is intended for releasing unmanaged resources, and you don’t generally want an object to "hang around" after calling Dispose.

    Also you should implement a protected Dispose method (unless your class is sealed) and a finalizer as described in MSDN.

  2. Chris says:

    Joe,

    Yes, the Dispose Pattern should definitely be used for unmanged resources, but that doesn’t mean a Dispose method can’t have other legitimate uses.

    The above construct is only really useful if you need BigObject to hang around, but no longer need the internal array. This also allows you to clean up this object in a using clause.

    A finalizer and protected Dispose for this class are unnecessary.

  3. Kevin Westhead says:

    Another example of using null is in the implementation of Stack.Pop(). Items on the stack are held internally in an array, and when Pop is called the size of the stack is adjusted and the former final element in the array is set to null. This means the array doesn’t retain popped items and therefore doesn’t prevent the GC from reclaiming them when all other external references have gone.

  4. Chris says:

    Nice non-contrived example Kevin!

  5. G. Man says:

    I’m somewhat in agreement with Joe about Dispose being for unmanaged resources.

    Furthermore, Dispose is meant to be called when you are completely done with an object. You seem to be saying that you will call BigObject.Dispose and then continue to use BigObject. This is definitely non-intuitive and certainly is not allowed with any of the CLR types.

  6. Chris says:

    G. Man

    Dispose is not necessarily for when you are completely done with an object. According to the IDisposable MSDN page:

    "This method is, by convention, used for all tasks associated with freeing resources held by an object, or preparing an object for reuse." (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemIDisposableClassDisposeTopic.asp)

    You could imagine another method on BigObject that repopulates the internal array after Dispose has been called. This could be comparable to a Collection’s Clear() method.

    Granted, Dispose is most useful (and should be used) for unmanaged resources, but that doesn’t disqualify it from having other uses.

  7. Dan Golick says:

    Finalize must be used for unmanaged resources and should not be used for managed resources since the finalization order is not guaranteed.

    Dispose on the other hand can be freely used for managed resources. Any class that has a field with a disposable object should implement dispose.

    Dispose and using allow us to implement a "scoped destructor".

    So we can create a lock object that unlocks at the end of the using scope or a timer that stops when leaving the scope.

  8. Anonymous says:

    Chroniques d’Am&#233;thyste – Null ou pas null?

  9. Fantastic blog post. Very Very useful!

  10. Sara says:

    Can you give the URL of latest similar posts about assignment of NULL