This document assumes you are familiar with the “
Finalize” design pattern, which I’ve discussed in GC101 and GC102.
Microsoft invented the pattern of finalization with the sole purpose of making code safer. If a developer referencing a component instance forgets to dispose off the component properly (by calling
Dispose), the component will still be disposed off automatically by the GC.
Let me discuss a few negative effects of implementing
Finalize (these do not exist for components without finalizers):
- Object needs to be enlisted in “finalisation queue”.
- Object needs to be removed from “finalisation queue”.
- Object needs to be enlisted in “to be finalized queue”.
Finalize method needs to be called.
- Object needs to be removed from “to be finalized queue”.
- You have no way of telling when exactly the GC will call your finalizer.
These points give you a broad overview of the impact a
Finalize method has behind the scenes. As you can see: finalization has a big performance impact. Let’s discuss finalizers more in depth.
A single Finalization thread
The most significant performance clog is point 4 above. The .NET garbage collector (GC) has a single worker-thread that calls all the
Finalize methods of everything enlisted in the “to be finalized queue”. This thread runs with high priority, and if many components need finalization, this thread will block threads with a lower priority from executing. With lots of garbage to be collected, you are effectively launching a denial-of-service attack against your processor!
Enlisting / de-listing
Enlisting / de-listing in the queues mentioned above has a hit on performance. This could be acceptable for simple components, but the only way to have tighter control over the way it impacts on performance is by doing cleanup manually.
You have no control over when the GC fires your
Finalize() method. You do know when it is suitable for a cleanup: when the resource in question is no longer needed, and the application is in such a state that it can absorb the performance hit of cleaning up. By placing cleanup code in
Close() methods, you can cleanup manually at the most appropriate time.
No referenced objects
You can’t reference any named object in a
Finalize() method. Since there is no specific order in which objects are being finalized, there is no way of telling whether the object you wish to reference has already been GC’d. This leaves you with a somewhat limited capability in your finalizer.
Overall degraded garbage collection
It will take the GC more cycles to collect a finalizable object. The impact of this is greater than it initially seems. Not only will your object live longer, but also all other objects it has references to.
As the GC places your object in the “to be finalized” queue, it will also be promoted to Generation 1 of the GC. Generation 1 is collected less often than Generation 0, and so all managed / unmanaged resources referenced by your object will effectively hog the system memory more than necessary.
Why would anyone implement finalization?
Are there any motivations for it? Sure there are: to *ensure* cleanup of your object’s resources. This is the one and only motivation for implementing finalizers. If your component uses resources, they need to be freed after use.
If a developer using your component calls
Dispose() explicitly (assuming you call
GC.SuppressFinalize in the
Dispose method), the resources will be cleaned, and you have no worries! If a developer however forgets to dispose off your component explicitly, the GC thread will clean up the resources for him when it calls
Finalize. Since the time of finalization is random, this takes control out of your hands.
I see the road branching into two:
- Putting all cleanup code in your
Close) method puts a lot of responsibility on the shoulders of developers using your component. This is true especially if your component utilizes unmanaged resources, where failure to call
Dispose will result in memory leaks. The flipside is that it also empowers your developers to
Dispose off your object completely when most appropriate.
Finalize, and only clean unmanaged resources in the method. Clean all managed resources in the
The most widely accepted way is number 2. I beg to differ – let me tell you why:
If developers fail to call
Dispose on your component, the resultant memory leaks will surely be spotted during testing. (If not, the testing process needs a revision!) After spotting a leak, code can be changed to call
Dispose on your component, and a system with optimum performance is the product.
There are even a few complications in the way
Finalize is implemented in some of the framework classes, of which I shall write in a further article - keep an eye on my blog for an update.