Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Garbage Collection and Finalizers: Finer Points

In answering another question* on SO, and the subsequent comment discussion, I ran into a wall on a point that I'm not clear on.

Correct me on any point where I'm astray...

When the Garbage Collector collects an object, it calls that object's finalizer, on a separate thread (unless the finalizer has been suppressed, e.g. through a Dispose() method). While collecting, the GC suspends all threads except the thread that triggered the collection (background collection aside).

What isn't clear:

  1. Does the Garbage Collector wait for the finalizer to execute on that object before collecting it?
  2. If not, does it un-suspend threads while the finalizer is still executing?
  3. If it does wait, what happens if the finalizer runs into a lock being held by one of the suspended threads? Does the finalizer thread deadlock? (In my answer, I argue that this is bad design, but I could possibly see cases where this could happen)

* Link to the original question:
.NET GC Accessing a synchronised object from a finalizer

like image 519
James King Avatar asked Mar 07 '11 18:03

James King


People also ask

What is garbage collector and compare it to finalizer?

The garbage collector checks for objects that are no longer being used by the application. If it considers an object eligible for finalization, it calls the finalizer (if any) and reclaims the memory used to store the object.

What is SuppressFinalize?

SuppressFinalize() system method is designed to prevent calling the finalizer on the specified object. If an object does not have a destructor, invoking SuppressFinalize on this object has no effect and ReSharper flags such call as redundant. To learn more how the destructor works, see Object.

What is the difference between Finalize () and garbage collector in Java?

finalize() method in Java is a method of the Object class that is used to perform cleanup activity before destroying any object. It is called by Garbage collector before destroying the objects from memory. finalize() method is called by default for every object before its deletion.

What is the relationship between the Finalize method and garbage collection?

The Java finalize() method of Object class is a method that the Garbage Collector always calls just before the deletion/destroying the object which is eligible for Garbage Collection to perform clean-up activity.


2 Answers

Objects that contain a finalizer tend to live longer. When, during a collect, the GC marks an object with a finalizer as being garbage, it will not collect that object (yet). The GC will add that object to the finalizer queue that will run after the GC has finished. Consequence of this is that, because this object is not collected, it moves to the next generation (and with that, all objects it refers to).

The GC suspends all running threads. The finalizer thread on the other hand will run in the background while the application keeps running. The finalizer calls all finalize methods on all objects that are registered for finalization. After the finalizer method on an object has ran, the object will be removed from the queue, and from that point on the object (and possibly all objects it still references) is garbage. The next collection that cleans objects of the generation of that object will (at last) remove that object. Since objects that live in generation 2 are collected about 10 times as less as objects that live in generation 1, and gen 1 ten times as less as gen 0, it can take some time for such object is finally garbage collected.

Because the finalizer thread is just a simple thread that runs managed code (it calls the finalizers), it can block and even dead lock. Because of this it is important to do as little as possible in finalize methods. Because the finalizer is a background thread, a failing finalize method could even bring down the complete AppDomain (yuck!).

You could say that this design is unfortunate, but if you think about it, other designs where the framework cleans our mess effectively, are hard to imagine.

So, to answer your questions:

  1. Yes, only after the object is removed from the finalizer queue, the object will be garbage and the GC will collect it.
  2. The GC suspends all threads, even the finalizer queue.
  3. The finalizer queue can deadlock. Lock as little as possible inside finalize methods.
like image 20
Steven Avatar answered Oct 17 '22 08:10

Steven


Does the Garbage Collector wait for the finalizer to execute on that object before collecting it?

Your question is a bit ambiguous.

When the GC encounters a "dead" object that needs finalization, it abandons its attempt to reclaim the dead object's storage. Instead, it puts the object on a queue of "objects that I know need finalization" and treats that object as alive until the finalizer thread is done with it.

So, yes, the GC does "wait" until the finalizer is executed before reclaiming the storage. But it does not wait synchronously. It sounds like you're asking "does the GC synchronously call the finalizer right there?" No, it queues up the object to be finalized later and keeps on truckin'. The GC wants to quickly get through the task of releasing garbage and compacting memory so that the program proper can resume running ASAP. It's not going to stop to deal with some whiny object that is demanding attention before it gets cleaned up. It puts that object on a queue and says "be quiet and the finalizer thread will deal with you later."

Later on the GC will check the object again and say "are you still dead? And has your finalizer run?" If the answer is "yes" then the object gets reclaimed. (Remember, a finalizer might make a dead object back into a live one; try to never do that. Nothing pleasant happens as a result.)

Does it un-suspend threads while the finalizer is still executing?

I believe that the GC thaws out the threads that it froze, and signals the finalizer thread "hey, you've got work to do". So when the finalizer thread starts running, the threads that were frozen by the GC are starting up again.

There might have to be unfrozen threads because the finalizer might require a call to be marshalled to a user thread in order to release a thread-affinitized resource. Of course some of those user threads might be blocked or frozen; threads can always be blocked by something.

what happens if the finalizer runs into a lock being held by one of the suspended threads? Does the finalizer thread deadlock?

You betcha. There's nothing magic about the finalizer thread that prevents it from deadlocking. If a user thread is waiting on a lock taken out by the finalizer thread, and the finalizer thread is waiting on a lock taken out by the user thread, then you've got a deadlock.

Examples of finalizer thread deadlocks abound. Here's a good article on one such scenario, with a bunch of links to other scenarios:

http://blogs.microsoft.co.il/blogs/sasha/archive/2010/06/30/sta-objects-and-the-finalizer-thread-tale-of-a-deadlock.aspx

As the article states: finalizers are an extremely complex and dangerous cleanup mechanism and you should avoid them if you possibly can. It is incredibly easy to get a finalizer wrong and very hard to get it right.

like image 120
Eric Lippert Avatar answered Oct 17 '22 07:10

Eric Lippert