When does an object become available for garbage collection?

As we saw last time, garbage collection is a method for simulating an infinite amount of memory in a finite amount of memory. This simulation is performed by reclaiming memory once the environment can determine that the program wouldn't notice that the memory was reclaimed. There are a variety of mechanism for determining this. In a basic tracing collector, this determination is made by taking the objects which the program has definite references to, then tracing references from those objects, contining transitively until all accessible objects are found. But what looks like a definite reference in your code may not actually be a definite reference in the virtual machine: Just because a variable is in scope doesn't mean that it is live.

class SomeClass {
 string SomeMethod(string s, bool reformulate)
  OtherClass o = new OtherClass(s);
  string result = Frob(o);
  if (reformulate) Reformulate();
  return result;

For the purpose of this discussion, assume that the Frob method does not retain a reference to the object o passed as a parameter. When does the OtherClass object o become eligible for collection? A naïve answer would be that it becomes eligible for collection at the closing-brace of the SomeMethod method, since that's when the last reference (in the variable o) goes out of scope.

A less naïve answer would be that it become eligible for collection after the return value from Frob is stored to the local variable result, because that's the last line of code which uses the variable o.

A closer study would show that it becomes eligible for collection even sooner: Once the call to Frob returns, the variable o is no longer accessed, so the object could be collected even before the result of the call to Frob is stored into the local variable result. Optimizing compilers have known this for quite some time, and there is a strong likelihood that the variables o and result will occupy the same memory since their lifetimes do not overlap. Under such conditions, the code generation for the statement could very well be something like this:

  mov ecx, esi        ; load "this" pointer into ecx register
  mov edx, [ebp-8]    ; load parameter ("o") into edx register
  call SomeClass.Frob ; call method
  mov [ebp-8], eax    ; re-use memory for "o" as "result"

But this closer study wasn't close enough. The OtherClass object o becomes eligible for collection even before the call to Frob returns! It is certainly eligible for collection at the point of the ret instruction which ends the Frob function: At that point, the Frob has finished using the object and won't access it again. Although somewhat of a technicality, it does illustrate that

An object in a block of code can become eligible for collection during execution of a function it called.

But let's dig deeper. Suppose that Frob looked like this:

string Frob(OtherClass o)
 string result = FrobColor(o.GetEffectiveColor());

When does the OtherClass object become eligible for collection? We saw above that it is certainly eligible for collection as soon as FrobColor returns, because the Frob method doesn't use o any more after that point. But in fact it is eligible for collection when the call to GetEffectiveColor returns—even before the FrobColor method is called—because the Frob method doesn't use it once it gets the effective color. This illustrates that

A parameter to a method can become eligible for collection while the method is still executing.

But wait, is that the earliest the OtherClass object becomes eligible for collection? Suppose that the OtherClass.GetEffectiveColor method went like this:

Color GetEffectiveColor()
 Color color = this.Color;
 for (OtherClass o = this.Parent; o != null; o = o.Parent) {
  color = BlendColors(color, o.Color);
 return color;

Notice that the method doesn't access any members from its this pointer after the assignment o = this.Parent. As soon as the method retrieves the object's parent, the object isn't used any more.

  push ebp                    ; establish stack frame
  mov ebp, esp
  push esi
  push edi
  mov esi, ecx                ; enregister "this"
  mov edi, [ecx].color        ; color = this.Color // inlined
  jmp looptest
  mov ecx, edi                ; load first parameter ("color")
  mov edx, [esi].color        ; load second parameter ("o.Color")
  call OtherClass.BlendColors ; BlendColors(color, o.Color)
  mov edi, eax
  mov esi, [esi].parent       ; o = this.Parent (or o.Parent) // inlined
  // "this" is now eligible for collection
  test esi, esi               ; if o == null
  jnz loop                    ; then rsetart loop
  mov eax, edi                ; return value
  pop edi
  pop esi
  pop ebp

The last thing we ever do with the Other­Class object (presented in the Get­Effective­Color function by the keyword this) is fetch its parent. As soon that's done (indicated at the point of the comment, when the line is reached for the first time), the object becomes eligible for collection. This illustrates the perhaps-surprising result that

An object can become eligible for collection during execution of a method on that very object.

In other words, it is possible for a method to have its this object collected out from under it!

A crazy way of thinking of when an object becomes eligible for collection is that it happens once memory corruption in the object would have no effect on the program. (Or, if the object has a finalizer, that memory corruption would affect only the finalizer.) Because if memory corruption would have no effect, then that means you never use the values any more, which means that the memory may as well have been reclaimed out from under you for all you know.

A weird real-world analogy: The garbage collector can collect your diving board as soon as the diver touches it for the last time—even if the diver is still in the air!

A customer encountered the Call­GC­Keep­Alive­When­Using­Native­Resources FxCop rule and didn't understand how it was possible for the GC to collect an object while one of its methods was still running. "Isn't this part of the root set?"

Asking whether any particular value is or is not part of the root set is confusing the definition of garbage collection with its implementation. "Don't think of GC as tracing roots. Think of GC as removing things you aren't using any more."

The customer responded, "Yes, I understand conceptually that it becomes eligible for collection, but how does the garbage collector know that? How does it know that the this object is not used any more? Isn't that determined by tracing from the root set?"

Remember, the GC is in cahoots with the JIT. The JIT might decide to "help out" the GC by reusing the stack slot which previously held an object reference, leaving no reference on the stack and therefore no reference in the root set. Even if the reference is left on the stack, the JIT can leave some metadata behind that tells the GC, "If you see the instruction pointer in this range, then ignore the reference in this slot since it's a dead variable," similar to how in unmanaged code on non-x86 platforms, metadata is used to guide structured exception handling. (And besides, the this parameter isn't even passed on the stack in the first place.)

The customer replied, "Gotcha. Very cool."

Another customer asked, "Is there a way to get a reference to the instance being called for each frame in the stack? (Static methods excepted, of course.)" A different customer asked roughly the same question, but in a different context: "I want my method to walk up the stack, and if its caller is OtherClass.Foo, I want to get the this object for OtherClass.Foo so I can query additional properties from it." You now know enough to answer these questions yourself.

Bonus: A different customer asked, "The Stack­Frame object lets me get the method that is executing in the stack frame, but how do I get the parameters passed to that method?"

Comments (13)
  1. rs says:

    Does that mean that if Frob were implemented as

    string Frob(OtherClass o)


    string result = "thefrob";


    and the compiler did some optimizations, the object o could be gone even before

    new OtherClass(s); returned?

  2. Henke37 says:

    The answer to the bonus question is "Who said that the parameters where saved?" Only keeping track of stuff that you care about and so on. Same as the command line arguments. And it's not unheard of that people change the values passed in during processing.

  3. Marquess says:


    Depending on what the constructor does (and the runtime of course), it may never be created.

  4. gibwar says:

    Thanks for the explanation of the directives. I've heard those before but never fully understood how they happened. Granted, I've never had a case where I had to worry about that or walk up the stack for something (untrustworthy anyway as you've previously pointed out) but the real icing on the cake was the examples in assembly. Once I saw the assembly examples it all made sense.

  5. Barry Kelly says:

    It's great that the knowledge about this is spreading inside MS :) I wrote a post 4 years ago on this topic – blog.barrkel.com/…/not-so-lazy-garbage-collector.html – relating to an access violation bug that occurred with the Ping class in .NET on AMD processors because of this exact problem.

  6. nathan_works says:

    If you need to walk the stack in your C# application, odds are you're doing something very, very, wrong.

  7. tobi says:

    Answer: It is not possible to always return the parameters as they may have been collected before the method started executing.

  8. mikeb says:

    This garbage collection behavior has caught some people using timers – they'd store a reference to a new timer instance in a local, do some work that relied on the timer, and expect the timer to live until the method returned (or even longer).  The notes and example for the System.Timers.Timer class now explicitly discuss this problem: msdn.microsoft.com/…/system.timers.timer.aspx

    However, some timers manage to keep themselves referenced or otherwise ineligible for collection when they're active (System.Windows.Form.Timer documents this as part of the Enabled property) – so there can be a bit of confusion about what you might need to do to make sure timers stick around (or get collected).

  9. Daniel says:

    @rs: yes, it's possible for an object to be collected while its constructor is still running.

    That can make for a funny debugging session when the finalizer releases an unmanaged object that's still being initialized.

    Remember kids: if you have unmanaged resources, always put them into their own class, and make that as simple as possible: use SafeHandle. The Microsoft-suggested way to mix managed and unmanaged resources in a single class (void Dispose(bool disposing)) is a sure way to introduce race conditions between your code and the finalizer thread.

  10. Curt Nichols says:

    There's an interesting variation on the subject of when an object is available for collection: if you've compiled with /debug the JITter will, in essence, hold references through the end of the current method; this makes for easier debugging. If I recall correctly this behavior also holds if a method is first jitted while runder under a debugger. This is markedly different that the normal /release behavior. If you run off to write some sample code to see what Raymond has described in action just don't use debug code, you won't be successful.


  11. Joshua says:

    Ah, getting object and methods from stack.

    I wrote code to do that in an unmanaged world once. It wasn't always right as the stack slots are just local variables and can be clobbered by the function.

    It got really fun when I was compiled with frame pointers and assumed everybody in between was (they weren't).

  12. Gabe says:

    I seem to recall hearing that some JavaScript engine (probably any one that tries to optimize) had to run really slow in the event that it detected any part of the program using the stack-walking functionality.

    Although if you're walking the stack in a C# application, I imagine that either you're implementing a debugger (in which case you'd want all the parameter information and such) or coroutines (in which case you wouldn't).

  13. Pierre B. says:

    Seems to me this is due to dubious language specification. A reference declared in a given scope should live as long as the scope, even in the face of optimizations. The optimizations should only kick in if it can prove that there are other references to the object.

    You cannot claim that it doesn't matter because the program cannot tell the difference. Obviously, it can when finalizers enter the picture.

    Other messages in this thread have convinced me that the dubious feature of optimizing when a reference vanish is not worth the hassles and obscure bugs related to it. The worst part is that you cannot fix the bug simply by adding yet more references to the object, as the optimizer can be as powerful as it wishes in the face of a free-for-all language spec.

Comments are closed.

Skip to main content