GC and Unmanaged Code
Inside the .NET applications most types we use need only memory for operating, but there are other common types that require more than just memory, they can also make use of native resources in addition to memory, we will refered to them as unmanaged resources
Unmanaged resources are present in common scenarios like opening a file for reading/writing (FileStream), database connections (SqlConnection), Network communications (Socket), multithreading implementations (Mutex), etc… Although these are in fact .NET classes, they are only mere wrappers over unmanaged native resources.
This kind of resources are not governed by GC and must be directly clean up once the execution has ended with them. Finalization is the mechanism provided by the CLR that allows an object to release the native resources related to it before the CG reclaims the object’s memory. By default, any CLR type that wraps a native resource, like a database connection, network connection, socket, mutex, etc.. must support finalization. Finalization implies additional performance overload at CG because potential finalizers means extra steps to be done at marking phase.
When the CG identifies and object as a garbage if this object implement a finalize method then a new pointer to this object will be placed at Finalization Queue, after that the queue will be scanned and every found pointer will be move to a more specifically internal data structure known as Freachable Queue, after the compaction phase an special CLR thread empties the Freachable Queue by execution the Finalize method
Finalizers (Destructors)
Finalize() method at c# environment is also known as destructor and it’s implemented by placing a tilde symbol (~) before the class-name
internal sealed class MyClass{
~MyClass() {
// The code here is inside the Finalize method
}
}
From an internal point of view the Finalize method commonly will invoke the Win32 CloseHandle function, passing the handle of every native resource, if call to Finalize performed over an object that implements a native resource fail, then native resource will not be released and will generate a resource leak that will long until the process is killed. In this scenario is useful CriticalFinalizerObject abstract class which guaranteed finalization executions by providing an special treatment. This privileged .NET class has three specific features that applies to any derived type: First one has to do with the fact that CLR always invokes the Finalize methods over all CriticalFinalizerObject-derived types after invoking the Finalize methods correspoding to the rest of types. Second one is directly related with the compilation time of CriticalFinalizerObject-derived types, CLR is in charge of performing an eager-compilation over all Finalize methods in the inheritance hierarchy for CriticalFinalizerObject-derived types; this is a key point because provide a deterministic certainty that native resource will be released — in some low memory scenarios it would be impossible for CLR to find enough memory for compliling Finalize method and creating a potential resource leak, eager compilation of CriticalFinalizerObject classes avoids this possibility because CLR perform JIT-compilation when creating the object. In addition, CriticalFinalizerObject derived classes provides another security frontier for those cases in which current AppDomain is suddenly killed by a parent application releasing native resources.
When Finalize Methods are Called
The invocation of Finalize methods can not be directly performed, only are fired as a completion of Garbage Collector process, so Finalization is a non-deterministic method, the process itself depends on garbage collector to discover when an object is refered; in esence Finalize method will be indirecly invoked in the following scenarios
- Generation 0 is full : This is the most common event for firing Finalize methods; :
- Low memory conditions: CLR use some Windows internal notification system that helps to prevent than programs stop working under low memory conditions, CLR responds to this mechanism running a garbage collection.
- Explicit invocation of GC.Collect() method: In some ocassions this can make sense
- When CLR unloads an AppDomain
- CLR is shutting down
The use of Finalize method must be applied with responsibility because increase the memory pressure avoiding the memory and resources related to be released until the second garbage collector round. At this point it worth to mention that CLR use an specific Thread for invoking the Finalize methods
More about Finalization
The aparent simplicity of Finalization implementation disappears when we take a look about what is really happen behind the scenes. From the relation between Finalization Queue and Freachable one to the real numbers of needed GC-Collection for freeing wrappers of unmanaged resources, Finalization implies internal complexities that should be reviewed
When a new object is created, the new operator in in charge of allocating the corresponding memory into heap memory, this memory has to be initialized to make type usable; in addition , if this type implements the Finalize method, then a new pointer to this object is placed on the Finalization-List queue even before type´s constructor is invoked. This way, when GC-Collection starts with the making phase, GC will check if any of marking objects is in the finalization-list queue, if this is so then object will be moved to the Freachable Queue. At this point the process become interesting because all the items at Freachable Queue will be in fact, reachable, that is to say, not considered as garbage, so these items can be considered as resurrected. In consecuence it will needed a second round of GC for reclamining memory corresponding to these types (in fact, in some cases more than a second CG will be needed due to different generations)
Performance Concerns
Finalization techniques are great for avoiding memory leaks with unmanaged resources when memory from managed wrappers is reclaimed, but finalization can also hurt application peformance under specific scenarios. The most common factors to be taken in this line are the following ones:
- A little performance penalization is associated to objects with finalizers because it´s necesary to reference them at finalization queue
- As we have seen types implementing finalization reach the Generation 1, this means that these objects are more susceptible to the mid-life crisis issue
- May be the most relevant drawback related with finalization is the potential preassure on the finalizer queue. If application threads allocate objects at higher rate than finalizer thread is able to clear them then a memory leak will be raised due corresponding all objects that were not finalized
Finalization vs Dispose Pattern
Finalization is commonly known as a non-deterministic finalization, it depends on GC-Collections as an attached operations. The Finalize is not a public method, so it can not be invoked in a determinist way, on the other hand, due to GC dependency we can not be sure about when the Finalizers are really invoked. It would be nice to have the capacity to invoke this kind of funcionality in a determinist way, here is where the Dispose pattern comes to rescue.
The Disponse pattern define a collection of conventions that developers can make use of implemeting deterministic finalization. As a general rule, any type implementing Finalize method should also implement the Dispose one