Friday, October 16, 2009

What is the meaning of GC.KeepAlive()?

What is the purpose of GC.KeepAlive(object obj)? From the method name, it sounds like it will keep obj from being garbage collected after the call. In fact, the opposite is true, it will keep obj from being garbage collected before the call. On the face, that sounds like a pretty absurd method, doesn't it.

The problem is that the method is named incorrectly. Better, but more verbose, names would be GC.MakeExplicitlyElligibleForGarbageCollection() or GC.DontCollectObjectBeforeNow().

So, what's really going on here? As you are presumably well aware, memory is reclaimed in the .NET framework through garbage collection, which can happen at any time, for any object that has no outstanding references (but don't confuse with reference counting). For example:

public void SomeMethod()
{
 SomeClass o = new SomeClass();
 GC.Collect(); // no references to 0, it may be collected
 return;
}

Not very exciting, and makes sense, right? o isn't being used after the point of instantiation, so the collector is free to clean it up and reclaim the memory*.

But suppose that for some reason or another you need o to remain 'alive' until after SomeMethod() exits. In order to guarantee that, you would need to add some reference to 0 at the end of the method so that it can't be collected. That is the purpose of GC.KeepAlive().

So, all that GC.KeepAlive() does is create an artificial reference to 0 so that it can't be collected prior to the GC.KeepAlive() call:

public void SomeMethod_V2()
{
 SomeClass o = new SomeClass();
 GC.Collect(); // reference to 0 below, it will NOT be collected
 GC.KeepAlive(o);
 GC.Collect(); // no more references to 0, it may now be collected
 return;
}

So, hopefully the above makes it clearer what GC.KeepAlive() does, although we do need to figure out a better name...

*Note: In order to facilitate debuggers, when an application is compiled in Debug mode, the JIT will NOT collect locally-allocated instances until after the method exits. So, in this example, if compiled under Debug, o will never be collected until after SomeMethod() returns. In Release mode, locally-allocated instances can be collected after the point of last reference, even before the method has returned.