Sunday, April 12, 2009

Object Resurrection using GC.ReRegisterForFinalize


I have been thinking about writing this post for some time, but Easter weekend provided the right context to do so.

When I first encountered GC.ReRegisterForFinalize I was a bit baffled. The context help “Requests that the system call the finalizer for the specified object for which System.GC.SuppressFinalize(System.Object) has previously been called.” didn’t provide much clue as to why I would want to finalize an object more than once.

This is when I found out about object resurrection.

res·ur·rec·tion   (rěz'ə-rěk'shən)
  1. The act of rising from the dead or returning to life.
  2. The state of one who has returned to life.
  3. The act of bringing back to practice, notice, or use; revival.

This definition made perfect sense. The basic idea of using object resurrection is to handle scenarios where an object creation is very expensive for some reason (e.g. it makes system calls that take a lot of time or consumes a lot of resources). To avoid creating new objects and incur the creation cost again, you’d try to re-cycle older objects which have already done their job or in other words resurrect dead objects.

This can be done by using the following facts

  1. If an object is garbage (not-reachable) and has a finalizer, it is not collected in the first pass of the GC but is placed in the finalizer queue
  2. The finalizer thread calls the finalizers of each object in the finalizer queue and removes it from the queue.
  3. The next pass of the GC actually reclaims the object (as in de-allocate the memory)

In #2 above when the finalizer is executing if a new reference to the object is created and GC.ReRegisterForFinalize is called then the object becomes reachable and hence non garbage and so in #3 the GC will not reclaim it. Since GC.ReRegisterForFinalize is called, in the next cycle when the same object is available for collection #1 through #3 will again follow (so we set up the cycle).

Consider the scenario where MyClass is a class which is expensive to create. We want to setup a pool of these objects so that we can just pick up objects from this pool and once the object is done with, it is automatically put into that pool.

class MyClass
/// <summary>
/// Expensive class
/// </summary>
/// <param name="pool" />Pool from which the objects are picked up</param>

public MyClass(List pool)
Console.WriteLine("Expensive MyClass::ctor");
this.pool = pool; // Store the pool so we can use it later

public void DoWork()
Console.WriteLine("{0} Did some work", this.resurrectionCount);

/// <summary>
/// Finalizer method
/// </summary>

// automatically return to the pool, makes this object reachable as well

// Ensure next time this same finalizer is called again

private List<MyClass> pool;
private int resurrectionCount = 0;

// Create pool and add a bunch of these objects to the pool
List pool = new List();
pool.Add(new MyClass(pool));
pool.Add(new MyClass(pool));

// Client code
MyClass cl = pool[0]; // get the object at the head of the pool
pool.RemoveAt(0); // remove the object
cl.DoWork(); // start using it. Once done it will automatically go back to pool

At the start a bunch of these objects are created and put in this list of objects (taking a hit in startup time). Then as and when required they are removed from this list and put to work. Once the work is done they automatically get en-queued back to the pool as the finalizer call will ensure that.

Don’t do this

Even though the above mechanism seems pretty tempting, it is actually a very bad idea to really use this method. The major reason being that the CLR never guarantees when GC is run and post that when the finalizer will be run. So in effect even though a bunch of these objects have done their job it will be not be deterministic on when they will land up back in the queue.

If you really need to use object pooling a much better approach is to implement your special interface where you provide a method which is explicitly called by client code and is therefore deterministically controllable.

No comments: