The finalization mechanism

In this section, we will understand how the garbage collector performs finalization in the .NET Framework. To do finalization, it maintains two queues in the system:

  • The finalization queue: The finalization queue is a data structure maintained by the garbage collector, which contains a reference to all the objects in a managed heap that have implemented the finalize method. Using this queue, the garbage collector essentially identifies all the objects that it needs to call the finalize method for in order to clean up the unmanaged code before the object can itself be destroyed. 
  • The fReachable queue: The fReachable queue is a data structure maintained by the garbage collector. It contains a reference to all the objects in the managed heap, which, even though they don't have any reference with the application root, can be deleted. However, before deleting them, it must call the finalize method to clean up the unmanaged code. 

Let's try and understand this with the following example. Suppose we have an application wherein we have declared an object class, A, which has the finalize method. All other objects don't have the finalize method.

Please refer to the following representational diagram of the different structures that could be in the garbage collector:

These structures can be described as follows:

  • Program Scope: This represents the different objects that may be in the scope of the application root or, in other words, are being used in the particular block of the program.
  • Managed Heap: This represents the heap memory structure maintained by the garbage collector to allocate memory to the objects present in the program scope. There are two divisions in the managed heap. One is Generation 0, which is used for newly created short-lived objects, and another is Generation 1, which is used to save long-lived objects.
  • Finalization Queue: As indicated previously, this will contain a reference to all the objects in a managed heap that have an implementation of the finalize method.
  • fReachable Queue: As indicated previously, this will contain a reference to all the objects in a managed heap for which, although they are not used in the program scope, the garbage collector needs to call the finalize method before their memory can be reclaimed.

Take a look at the following steps:

  1. Declare the following two classes: SampleFinalizeClass and SampleNoFinalizeClass. Please note that the SampleFinalizeClass class has a finalize method:
public class SampleFinalizerClass
{
~SampleFinalizerClass()
{
}
}
public class SampleNoFinalizeClass
{
}
  1. Create three objects; one for SampleFinalizerClass and two for SampleNoFinalizerClass:
SampleFinalizerClass b = new SampleFinalizerClass();
SampleNoFinalizeClass c = new SampleNoFinalizeClass();
SampleNoFinalizeClass d = new SampleNoFinalizeClass();

As objects b, c, and d are newly created objects, they will be added to generation 0 in the managed heap. While doing so, the garbage collector will also recognize that object b needs to have an additional call of the finalize method before it can be cleared. It will make this entry in the finalization queue by adding a reference to object b. The following diagram indicates what this would look like: 

  1. Pass the execution to another function by passing it to object c. The following is the code snippet for this:
GarbageCollectorFinalize(c);
// Please note that in the example cs file, these two lines will be in the different blocks of the program
static private void GarbageCollectorFinalize(SampleNoFinalizeClass a)
{
}

Now, suppose that, during program execution when the control is at the GarbageCollectorFinalize function, the garbage collector gets called. The garbage collector will identify that object d is no longer required and, therefore, its memory can be reclaimed. However, object c is being still referenced. Therefore, it will make an assumption that this could be a long-lived object and will thus promote the object to generation 1. 

For object b, it will recognize that it's not referenced now; however, it does have a finalize method and so cannot be cleaned. Therefore, it keeps object b in memory for now. However, it removes the entry in the Finalization Queue and adds an entry in the fReachable Queue so that the variable can be cleared later.

Object b, as it cannot be removed from memory in the same way as object c, will also be promoted to Generation 1. The following shows this:

This illustrates the following:

  • Even though object b may not still be required, it will be persisted for a longer period of time in the memory.
  • As in the previous example, the garbage collector will need to execute another iteration in order to clear these objects from the memory.
  • Unused objects that are implementing finalize may be moved to a higher generation.

Due to these reasons, it's highly advisable that whenever we need to declare an object that has the finalize method, we must implement the IDisposable interface.

Before we go on to look at the IDisposable interface, let's take a look at the following code implementation illustrating how the Finalizer function works in C#:

  1. Consider the following code implementation, in which we declare a Finalizer class and then add a Finalizer function to it:
public class Finalizer
{
public Finalizer()
{
Console.WriteLine("Creating object of Finalizer");
}
~Finalizer()
{
Console.WriteLine("Inside the finalizer of class Finalizer");
}
}

Note that we have added text in both the Finalizer class constructor and in the Finalizer method.

  1. Use the following code snippet to create an object of this class. Additionally, note that we have set a null value to the object. Setting a null value signifies that the object is no longer required in the application: 
Finalizer f = new Finalizer();
f = null;
Console.ReadLine();

Note that, by using the Console.ReadLine() syntax, we are preventing the application from terminating. We have done this to analyze the output coming from the program. When we execute .exe, we get the following output:

In the preceding output, we are only getting the message from the constructor of the Finalizer class. Even though the object has been set as null, the finalizer of object f has not been executed yet.

This is due to the fact that we cannot specify when the garbage collector kicks in. Now, press Enter in the .exe execution. Notice that the program stops the execution; however, before it terminates, the finalizer is called to reclaim the memory of object f:

This proves we were right about finalizers, which we discussed earlier in this section. Even though object f was no longer needed in the application, it was still kept in the managed heap memory until the garbage collector executed the Finalizer method. 

  1. Now, add the following code to implicitly call the garbage collector and note that the finalize method is called immediately:
Finalizer f = new Finalizer();
f = null;
GC.Collect();
Console.ReadLine();

If we execute the program now, we will see the output from the finalizer of the Finalizer class, illustrating that the garbage collector immediately reclaimed the memory:

When we call the GC.Collect() method, internally, it calls the finalizers for all the objects that are no longer required. Thus we get the message, Inside the finalizer of class Finalizer.

In the preceding code example, we discovered that if we use Finalizer, we may have some performance implications in the program. Although we can use the GC.Collect() command to implicitly call the garbage collector, even that can cause some lag in the program. To overcome these issues, C# is capable of using the IDisposable interface in such circumstances. In the next section, we will understand how we can implement this interface and how it helps us achieve better performance.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.150.123