top of page

Introduction of Garbage Collector in C#

Garbage Collector is nothing but, it is a feature provided by CLR which helps us to clean or destroy the unused managed objects. By cleaning or destroying those unused managed objects, basically reclaims the memory.


When a dot net application runs, it creates several objects and at a given moment of time, it may possible that the application may not use some of those objects. So, for those objects, Garbage Collector runs continuously as a background thread and at specific interval time, it checks whether there are any unused managed objects and if it finds it simply clean those objects and reclaims the memory.


Note: The Garbage Collector will destroy only the unused managed objects. It does not clean unmanaged objects. If you want to learn what exactly are managed and unmanaged objects, please read our previous article.


Garbage Collector Generations in .NET:

Let us understand what Garbage Collector Generations are and how does it affect Garbage Collector performance? There are three generations. They are Generation 0, Generation 1, and Generation 2.


Understanding Generation 0, 1, and 2:

Let say you have a simple application called App1. As soon as the application started it creates 5 managed objects. Whenever any new objects (fresh objects) created, they are moved into a bucket called Generation 0. For better understanding please have a look at the following image.


We know our hero Mr. Garbage Collector runs continuously as a background thread to check whether there are any unused managed objects so that it reclaims the memory by cleaning those objects. Now, let say two objects (Object1 and Object2) are not needed by the application. So, Garbage Collector will destroy these two objects (Object1 and Object2) and reclaims the memory from Generation 0 bucket. But the remaining three objects (Object3, Object4, and Object5) are still needed by the application. So, the Garbage collector will not clean those three objects. So, what Garbage Collector will do is, he will move those three managed objects (Object3, Object4, and Object5) to Generation 1 bucket as shown in the below image.



Now, let say your application creates two more fresh objects (Object6 and Object7). As fresh objects, they should be created in Generation 0 bucket as shown in the below image.



Now, again Garbage Collector runs and it comes to Generation 0 bucket and checks which objects are used. Let say both objects (Object6 and Object7) are unused by the application, so it will remove both the objects and reclaims the memory. Now, it goes to the Generation 1 bucket, and checks which object are unused. Let say Object4 and Object5 are still needed by the application while objects3 not needed. So, what Garbage Collector will do is, it will destroy Object3 and reclaims the memory as well as it will move Objec4 and Object5 to Generation 3 bucket which is shown in the below image.




What are Generations?

Generations are nothing but, they will define how long the objects are staying in the memory. Now the question that should come to your mind is why do we need Generations?


Why do we need Generations?

Normally, when we are working with big applications, they can create thousands of objects. So, each of these objects, if garbage collector goes and checks if they are needed or not, it’s really pain or it’s a bulky process. By creating such generations what it means if an object in Generation 2 buckets it means the Garbage Collector will do fewer visits to this bucket. The reason is, if an object move to Generation 2, it means it will stay more time in the memory. It’s no point going and checking them again and again.


So, in simple words, we can say that Generations 0, 1, and 2 will helps to increase the performance of the Garbage Collector. The more the objects in Gen 0, the better the performance and the more the memory will be utilized in an optimal manner.


How using a destructor in a class we end up in a double garbage collector loop?

As we already discussed garbage collectors will only clean up the managed code. In other words, for any kind of unmanaged code, for those codes to clean up has to be provided by unmanaged code, the garbage collector does not have any control over them to clean up the memory.


For example, let say you have a class called MyClass in VB6, then you have to expose some function let say CLeanUp() and in that function, you have to write the logic to clean up the unmanaged code. From your dot net code, you simply need to call that method (CLeanUp()) to initiate the clean-up.


The point or the section from where you would like to call the Clean-Up is the destructor of a class. This looks to be the best place to write the clean-up code. But, there is a big problem associated with it when you write clean-up in a destructor. Let us understand what the problem is?


When you define a destructor in your class, the Garbage Collector before disposing of the object, will go and ask the question to the class, do you have a destructor, if you have a destructor, then move the object to the next generation bucket. In other words, it will not clean up the object having destructor at that moment itself even though it is not used. So, it will wait for the destructor to run, and then it will go and clean up the object. Because of this, you will find more objects in generation 1 and Generation 2 as compared to Generation 0.


Example: Using Destructor

Please create a console application and then copy and paste the following code in it in the Program class.

using System;
namespace GCDemo
{
 class Program
 {
 static void Main(string[] args)
 {
 for(int i = 0; i <= 1000000; i++)
 {
                MyClass obj = new MyClass();
 }

            Console.Read();
 }
 }

 public class MyClass
 {
        ~MyClass()
 {
  //Unmanaged code clean up
 }
 }
}

So, if you writing the clean-up code in your destructor, then you will end up creating more objects in Generation 1 and Generation 2 which means you are not utilizing the memory properly.


How to Overcome the above Problem?

This problem can be overcome by using something called finalize dispose pattern. In order to implement this, your class should implement the IDisposable interface and provide the implementation for the Dispose method. Within the Dispose method, you need to write the clean-up code for unmanaged objects and in the end, you need to call GC.SuppressFinalize(true) method by passing true as the input value. This method tells suppress any kind of destructor and just go and clean up the objects. For better understanding, please have a look at the following image.


Once you have used to object, then you need to call the Dispose method so that the double garbage collector loop will not happen as shown below.




The complete code is given below.

using System;
namespace GCDemo
{
 class Program
 {
 static void Main(string[] args)
 {
 for(int i = 0; i <= 1000000; i++)
 {

                MyClass obj = new MyClass();
                obj.Dispose();

 }

            Console.Read();
 }
 }

 public class MyClass : IDisposable
 {
        ~MyClass()
 {
 
 }
 public void Dispose()
 {
  //Unmanaged code clean up
            GC.SuppressFinalize(true);
 }
 }
}


Now, the question that should come to your mind is why the destructor is there? The reason is as a developer you may forget to call the Dispose method once you use the object. In that case, the destructor will invoke and it will go and clean-up the object.


Source: dotnet tutorial


The Tech Platform

0 comments
bottom of page