views:

37

answers:

2

Hi,

1) Does adding a Dispose(), or finalize method, have any impact on making an object be GC'd soon? I ask this as my manager wrote some code, adding a finalize() method, in the hope it would be GC'd.

But I argued that the object has to be "marked" for collection first, if it meets certain criteria.

2) Does writing "o = null" (where o is a ref type) have any impact on making an object be GC'd sooner? Either in C++ or Java/C#.

Thanks

A: 

Java:

1) no, finalize() is called when the garbage collector determines that the object is GC-able - it has no bearing on when GC happens, or whether the object will be GC-ed on a given run. In fact, implementing finalize() delays an object from being GCed - the first GC pass determines that the object is GC-able (i.e. finalize() should be called) and it isn't until the second pass that the object is freed - assuming that the finalizer didn't create a new reference to the object!

2) holding onto references will delay GC, so breaking a reference can allow an object to be GCed earlier if the object holding the reference is still reachable. It's no guarantee that the object will be GCed earlier, though.

SimonJ
+2  A: 

Note, this answer is regarding .NET. I don't know how Java does this, at all.

Let's start with the addition of a finalizer, or rather, what happens when an object without one is collected.

When GC runs, and discovers objects for which there are no rooted references, they're collected.

However, if you add a finalizer to the class, when GC discovers that the object is eligible for collection, it is placed on a list, because it has a finalizer.

This list is processed besides the normal GC, and since the object now has a rooted reference (that list), it is temporarily not eligible for collection. The list is processed by iterating over it and calling all the finalizers. There's lots of details here that I'm glossing over.

Once the finalizer for an object has been called, the object is removed from the list. Sometimes later, when GC again discovers that object, though it still has the finalizer method in its type, the object has been marked so that the finalizer no longer needs to run, and the object is now collected as though it didn't have a finalizer to begin with.

So, actually, adding a finalizer will not make the object be collected sooner, rather it will actually make the object be collected later.

The only way to make an object eligible for collection is to remove all rooted references to it.

Calling Dispose also has no meaning in this regard. Dispose is just a method call, and calling it does in no way mark the object as eligible for collection. If, after calling Dispose, you still have rooted references to the object, it will be left in memory and not collected.

However, if your class has a Dispose method, and a finalizer, the Dispose method typically unregisters the object from finalization. Basically you say "Dispose has now taken care of everything that the finalizer would do, so there's no longer any point in calling the finalizer". If it does this, calling the Dispose method, and then removing all live references to the object, will make it both eligible for collection, and skip the finalization step.

On to your second question.

As I said above, you need to remove all rooted references to the object. A rooted reference is a reference that can be tracked back to something that lives for the duration of the program, be it static fields, local variables on still live call stacks, etc. Once all those are gone, the object is eligible for collection.

But the GC in .NET is really aggressive. The JITter will store information alongside the code telling GC which portions of a method that uses the local variables, and if a variable is no longer used, for instance for the last portion of a method, even though the variable still references an object, that variable is considered to be unnecessary, and thus the object can be collected.

For instance, here:

public void Test()
{
    object o = new object();
    // do something else
}

In this case, as long as the o variable is no longer used in the method, during that "do something else" code, the object in it can be collected.

The JITter will detect when the program is running in the debugger, and then artificially extend the lifetime of all variables to the end of their scopes, so that you can inspect variables even though they technically aren't considered "live" any more. But when not running in the debugger, that o above isn't explicitly nulled doesn't mean anything, the object can still be collected.

Now, if o was a static field, which lives for much longer than a method call, then yes, explicitly setting it to null will of course help, because now you're removing rooted references to the object.

Also, if the variable is re-used later in the method, you can help make the current object eligible for collection by setting it to null. (note I'm not entirely sure about this, perhaps the JITter can see that the current value is not needed, so it can be collected because later on you overwrite the contents anyway)

So to summarize:

  • Don't add a finalizer unless you need it
  • Calling Dispose has no bearing on how soon the object becomes eligible for collection
  • To "mark" an object as eligible for collection, get rid of all rooted references to it
  • You can help with collection by explicitly setting variables and fields to null, but for local variables no longer in used by a method, it may not be necessary (but won't hurt)
Lasse V. Karlsen
Re. 1, Java is similar - objects with finalize methods aren't GCed until the next pass of the collector.
SimonJ