When does an object become available for garbage collection?

Raymond Chen

As we saw last time,garbage collection is a method forsimulating an infinite amount of memoryin a finite amount of memory.This simulation is performed by reclaiming memory once the environmentcan determine that the program wouldn’t notice that the memory wasreclaimed.There are a variety of mechanism for determining this.In a basic tracing collector,this determination is made by taking the objects which theprogram has definite references to, then tracing references from thoseobjects, contining transitively until all accessible objects are found.But what looks like a definite reference in your code may not actuallybe 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 referenceto the object o passed as a parameter.When does the OtherClass object obecome eligible for collection?A naïve answer would be that it becomes eligible for collectionat 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 collectionafter the return value from Frob is stored to the localvariable result, because that’s the last line of code whichuses the variable o.

A closer study would show that it becomes eligible for collectioneven 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 callto 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 variableso and resultwill occupy the same memory since their lifetimes do not overlap.Under such conditions,the code generation for the statement could very well be somethinglike 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 obecomes eligible for collection even before the call to Frobreturns!It is certainly eligible for collection at the point of the retinstruction which ends the Frob function:At that point,the Frob has finished using the object and won’t accessit again.Although somewhat of a technicality, it does illustrate that

An object in a block of codecan become eligible for collection during execution of a functionit 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 forcollection?We saw above that it is certainly eligible for collection as soon asFrobColor returns, because the Frobmethod doesn’t use o any more after that point.But in fact it is eligible for collection when the callto GetEffectiveColor returns—even before theFrobColor method is called—because the Frobmethod doesn’t use it once it gets the effective color.This illustrates that

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

But wait, is that the earliest the OtherClass objectbecomes eligible for collection?Suppose that the OtherClass.GetEffectiveColor methodwent 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 thispointer 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­Classobject (presented in the Get­Effective­Colorfunction by the keyword this)is fetch its parent.As soon that’s done(indicated at the point of the comment, when the line is reachedfor the first time),the object becomes eligible for collection.This illustrates the perhaps-surprising result that

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

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

A crazy way of thinking of when an object becomes eligible forcollection is that it happens oncememory corruption in the objectwould have no effect on the program.(Or, if the object has a finalizer, that memory corruption wouldaffect 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 beenreclaimed out from under you for all you know.

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

A customer encountered theCall­GC­Keep­Alive­When­Using­Native­ResourcesFxCop ruleand didn’t understand how it was possible for the GC tocollect 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 rootset is confusing the definition of garbage collection with itsimplementation.“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 forcollection, but how does the garbage collector know that?How does it know that the this object is not usedany 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 byreusing the stack slot which previously held an objectreference,leaving no reference on the stack and therefore no referencein the root set.Even if the reference is left on the stack, the JIT can leavesome metadata behind that tells the GC, “If you see the instructionpointer in this range, then ignore the reference in this slotsince it’s a dead variable,”similar to how in unmanaged code on non-x86 platforms, metadatais used to guide structured exception handling.(And besides, the this parameter isn’t even passedon 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 calledfor each frame in the stack? (Static methods excepted, of course.)”A different customer asked roughly the same question, but ina different context:“I want my method to walk up the stack, and if its caller isOtherClass.Foo, I want to get the thisobject for OtherClass.Foo so I can query additionalproperties 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 thatis executing in the stack frame,but how do I get the parameters passed to that method?”