Posted by: leppie | February 22, 2011

GC.SuppressFinalize and .NET Object Finalizers (or Why printing from the finalizer is not a good idea)

So there has long been a heavy discussion about calling Dispose on IDisposable objects. This also requires that Dispose be implemented correctly, iow calling GC.SuppressFinalize(this).

After my umpteenth time saying it should NOT be needed to call Dispose, I got a response from a well-respected StackOverflow user that made me think about it more. Although he did not mention why I was wrong, I could just had to figure this one out.

As we know, IDisposable is recommended primarily to release unmanaged resources in a deterministic way for managed types. If implemented correctly, you need to call GC.SuppressFinalize in your Dispose method. GC.SuppressFinalize prevents the object’s finalizer to be called.

All good up to here?

So lets run a little test:

class Foo
{
  byte[] buffer = new byte[50 * 1024];

  ~Foo()
  {
    Console.WriteLine("Finalized");
  }

  public void Moo()
  {
    Console.WriteLine("Moosposed");
    GC.SuppressFinalize(this);
  }
}

class Program
{
  static void Main(string[] args)
  {
    while (true)
    {
      new Foo();
    }
  }
}

Run the above, and within seconds you should get an OutOfMemoryException. Funny, seems the GC is not doing it’s job, or the printing in the finalizer is causing it not to be collected? This can be ‘confirmed’ by removing that printing statement. Strange indeed why something that has no reference to the object can prevent it being finalized.

Now change the code to:

class Program
{
  static void Main(string[] args)
  {
    while (true)
    {
      new Foo().Moo();
    }
  }
}

Now the program runs happily till the cows come home, with barely any memory usage (under 18MB on my PC). So what is happening? Is GC.SuppressFinalize actually freeing the objects memory deterministically when called? It would appear so, but nowhere in any documentation is this extremely important fact noted. It also does not appear so, as adding GC.SuppressFinalize(this) to the finalizer after the printing statement also causes an OutOfMemoryException. Also, why is MS recommending calling GC.SuppressFinalize from the finalizer? This does not make sense. Can it be GC’d twice?

So all in all, one should never call Console.WriteLine() from within the finalizer else it will prevent the object from being considered for immediate garbage collection. What else can you not call from the finalizer? Clearly, when dealing with unmanaged resources, you will have to at least free them in your Dispose method, but if this is called from the finalizer, it seems you are in the same boat. What happens if your unmanaged component logs stuff to the console when you ‘close it’s handle’ ? Are you doomed?

I think the moral of the story is that one should at all costs prevent calling any unknown code (and Console methods) from the finalizer. This might be the reason why Dispose is pushed so heavily by MS.

On a side note, given we have ‘clean’ finalizers, it does not really matter if you call Dispose or not on an IDisposable object. The GC will take care of it, unless something happens like above.

I still stick to my point I made several months back. You should call Dispose, however it is not a must. It is only a must if it prints something or something that prevents the immediate GC of the object.

About these ads

Responses

  1. Remember that the finalizers are run on different thread. My guess is that GC works as expected, but you are creating the new objects on the main thread much more quickly then the GC can free them (because it has to call Console.WriteLine for each). To test this call GC.WaitForPendingFinalizers in the main loop.

    The second version of your code worked because SuppressFinalize has disabled the finalizer for the object and thus the Console.WriteLine was not called.

  2. Oh, and object can be GC’ed twice – it is called resurection. It happens when you store a reference to your object from the finalizer makeing it reachable again. It is really stupid thing to do, but the GC has to handle it.


Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Categories

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: