When GC.KeepAlive Doesn’t

The purpose of GC.KeepAlive(Object) is to tell the GC not to collect an object until a certain point.  For example:

class MyObject
   Console.WriteLine(“MyObject Finalized”);

   public static void Main()
      MyObject obj = new MyObject();
      GC.KeepAlive(obj); // ~MyObject will NOT be run before this call

KeepAlive will ensure ~MyObject will not get run before LongRunningMethod gets called.  This is useful if the long running method passes the object out to unmanaged code, for example.  In that case, you’ll want to keep the object from being collected by the GC until the method returns.

What’s the secret to KeepAlive?  Nothing.  It’s just a normal method with no side effects except holding a reference to an object.  Since it holds a reference, the JIT considers the object rooted until that point (if no other reference to this object exists and if you are not in debuggable code), and the GC will not collect it.

So when does KeepAlive not keep an object alive?  When it’s not called.  Ok, that was deliberately cryptic, let me illustrate using the MyObject class above: 

public static void Main()
   MyObject obj = new MyObject();
   while (true) 
      GC.Collect(); // force a collection to illustrate

One would expect that when run, there would be nothing printed to the screen, since KeepAlive keeps obj from getting collected.  But since KeepAlive is after a while(true) loop it’s actually unreachable.  Compiling the code will give you compiler warning CS0162: Unreachable code detected on the GC.KeepAlive(obj) line.  Since KeepAlive is not even being called, obviously it won’t hold the object live. However if you compile the code in debug mode, the JIT will extend lifetimes of references to the end of their enclosing method.  In this case, KeepAlive actually isn’t necessary.  But in the release case, a reference should be live until the last line of code that references it.  So why is obj getting finalized?

Looking at the IL for Main, we see what happened:

.method public hidebysig static void  Main() cil managed
  // Code size       21 (0x15)
  .maxstack  1
  .locals init ([0] class MyObject obj,
           [1] bool CS$4$0000)
  IL_0000:  nop
  IL_0001:  newobj     instance void MyObject::.ctor()
  IL_0006:  stloc.0
  IL_0007:  br.s       IL_0011
  IL_0009:  nop
  IL_000a:  call       void [mscorlib]System.GC::Collect()
  IL_000f:  nop
  IL_0010:  nop
  IL_0011:  ldc.i4.1
  IL_0012:  stloc.1
  IL_0013:  br.s       IL_0009
} // end of method MyObject::Main

Since the code after the while loop is considered dead, the compiler has actually not built it.  So the call to KeepAlive was optimized away, and is never actually called by the runtime.  The JIT then thinks it is no longer reachable after the while loop, and the GC is free to collect it.

Comments (6)

  1. Stephane Rodriguez says:

    I have a case with customers using my COM object in .NET code, and the COM object gets disconnected not because it’s a long running object, but because the application calls Application.DoEvents(). In other words, letting the app do its internal stuff kills a COM object.

    My only recommendation to the customers is to declare and instantiate the COM object as a class member, instead of just a local object. That fixes the problem.

    Needless to say, it’s ugly. And it does not scale…

  2. Оказывается, в некоторых случаях GC.KeepAlive может не срабатывать. Забавная нах

  3. Alex says:

    It’s matter not in GC.KeepAlive at all. If you put another code at this place it will not be executed either. What can you expect from unworking code?

  4. clyon says:

    Hi Alex

    The point is that there is nothing special about the KeepAlive method.  The JIT doesn’t special case it, and just having it in code doesn’t necessarily affect the lifetime of an object.


  5. Last update: June 13 , 2007 Document version 0.6 Preface If you have something to add, or want to take

  6. INFO: · "COM Descriptor Directory" part of the PE is responsible whether executable file is