Garbage Collection in C#.NET

Garbage Collection in C#.NET Framework

In this article, I am going to discuss Garbage Collection in C#.NET with Examples. Please read our previous article where we discussed Destructor in C# with Examples. At the end of this article, you will understand what is Garbage Collector in .NET Framework and how does it work? As part of this article, we are going to discuss the following pointers in detail.

  1. What is Garbage Collection in C#.NET?
  2. What are the different Generations of Garbage Collections?
  3. How to use .NET Memory Profiler to Check different Generations of Garbage Collection?
  4. How does using a destructor in a class end up in a double garbage collection loop?
  5. How we can solve the double loop problems using Finalize Dispose Patterns?
What is Garbage Collection in .NET Framework?

When a dot net application runs, lots of objects are created. At a given point in time, it is possible that some of those objects are not used by the application. Garbage Collector in .NET Framework is nothing but a Small Routine or you can say it’s a Background Process Thread that runs periodically and try to identify what objects are not being used currently by the application and de-allocates the memory of those objects.

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

Note: The Garbage Collector will destroy only the unused managed objects. It does not clean unmanaged objects. 

Managed and Unmanaged objects in .NET Framework:

Let us understand Managed and Unmanaged objects. Whenever we create any EXE (i.e. console application, windows application, etc.) or web application (i.e. ASP.NET MVC, Web API, ASP.NET, Class Library, etc.) in .NET Framework using visual studio and using any .NET supported programming language such as C#, VB, F#, etc., then these applications are run completely under the control of CLR (Common Language Runtime). That means, if your applications have unused objects, then CLR will clean those objects using Garbage Collector.

Now, let’s say, you have also used other third-party EXE in your .NET application like Skype, PowerPoint, Microsoft Excel, etc. These “EXEs” are not made in dot net, they are made using some other programming languages such as C, C++, Java, etc.

Managed and Unmanaged objects in .NET Framework

When you use these “EXEs” in your application, then these are not run by CLR. Even though you are running these “EXE” in dot net applications, they are going to run under their own environment. For example, if one EXE is developed using C or C++, then that EXE will run under the C or C++ runtime environment. In the same line, if the EXE is created using VB6, then it is going to run under the VB6 runtime environment.

What Exactly is the Managed and Unmanaged Code in .NET?

The codes which run under the complete control of CLR are called Managed Code in .NET Framework. These kinds of code (Managed code in C#) are run by a dot net runtime environment. If the dot net framework is not installed or if dot net runtime is not available, then these kinds of codes are not going to be executed. CLR will provide all the facilities and features of .NET to the managed code execution like Language Interoperability, Automatic Memory Management, Exception Handling Mechanism, Code Access Security, etc.

On the other hand, Skype, PowerPoint, and Microsoft Excel do not require dot net runtime, they run under their own environment. So, in short, the code (EXE, Web App) which not run under the control of CLR is called unmanaged code in .NET. CLR will not provide any facilities and features of .NET to the unmanaged code in C# execution like Language Interoperability, Automatic memory management, Exception handling mechanism, code access security, etc.

Garbage Collection Generations in C#.NET:

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

Understanding Generation 0, 1, and 2:

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

What is Garbage Collector in .NET Application

We know our hero Mr. Garbage Collector runs continuously as a background process thread to check whether there are any unused managed objects so that it reclaims the memory by cleaning those objects. Now, let’s 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. 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.

Garbage Collector Generations in .NET

Now, let’s 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.

Understanding Generation 0, 1, and 2

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

What are Generations?

What are Generations?

Generations are nothing but, 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 have three different kinds of generations?

Why do we need Generations?

Normally, when we are working with big applications, they can create thousands of objects. So, for each of these objects, if the garbage collector goes and checks if they are needed or not, it’s really painful or it’s a bulky process. By creating such generations what does it mean if an object in Generation 2 buckets means the Garbage Collector will do fewer visits to this bucket? The reason is that if an object moves 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.

Note: To give better clarity about the generations we are going to use a tool called .NET Memory Profiler. Now, I will show you how to download, install and use .NET Memory Profiler with C# Console Application to check and see how the objects are created in the different generations of Garbage Collector.

What is .NET Memory Profiler?

.NET Memory Profiler is a powerful tool for finding memory leaks and optimizing the memory usage in programs written in C#, VB.NET, or any other .NET Language. With the help of the profiling guides, the automatic memory analyzer, and specialized trackers, you can make sure that your program has no memory or resource leaks, and that the memory usage is as optimal as possible.

How to Download .NET Memory Profiler?

To download .NET Memory Profiler, please visit the following link.

https://memprofiler.com/

Once you click on the above link, it will open the following webpage. From the below Page, click on the Download Free Trial button as shown in the below image.

How to Download .NET Memory Profiler?

Once you click on the Download Free Trial button, it will open another page where it is asking you to enter your email address. If you want you can enter the email address else simply click on the Download button which will download the .NET Memory Profiler as shown in the below image.

Download .NET Memory Profiler EXE File

Once you click on the Download button, it will download the .NET Memory Profiler EXE and once you download the .NET Memory Profiler EXE, then click on the downloaded EXE file to install it. Once you click on the EXE file, it will open the following License Agreement Window. Simply check the checkbox and click on the Next button as shown in the below image.

Garbage Collection in C#.NET with Examples

Once you click on the Next button it will open the following Integrate with Visual Studio window. As I have installed Visual Studio 2017, 2019, and 2022, it shows me all the options and I want to use this .NET Memory Profiler with all the versions. So, I checked all the checkboxes and then click on the Next button as shown in the below image.

Garbage Collection in C#.NET with Examples

Once you click on the Next button, it will open the Ready to Install window. Simply click on the Install button as shown in the below image.

Garbage Collection in C#.NET Framework with Examples

Once you click on the Install button, it will ask you do you want to make changes to this computer, click Yes, so that it will start installing the .NET Memory Profiler on your machine. Once the installation is completed, you will get the following message. Click on the close button to close this.

Garbage Collection in C#.NET Framework with Examples

Creating a C# Console Application:

Now, create a console application with the name GarbageCollectionDemo in the D:\Projects\ directory using C# Language as shown in the below image.

Creating a C# Console Application

Now, copy and paste the following code into the Program class. Please note here we are not using a destructor.

using System;
namespace GarbageCollectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i <= 1000000; i++)
            {
                MyClass1 obj1 = new MyClass1();
                MyClass2 obj2 = new MyClass2();
                MyClass3 obj3 = new MyClass3();
            }

            Console.Read();
        }
    }

    public class MyClass1
    {
    }

    public class MyClass2
    {
    }

    public class MyClass3
    {
    }
} 

Now, build the solution and make sure there are no errors. Now, we will run this application using .NET Memory Profiler and will see the different generations of Garbage Collectors.

How to use .NET Memory Profiler to Run C# Console Application?

Open .NET Memory Profiler and once you open you will get the following window. From this window, click on the Profile Application option as shown in the below image.

How to use .NET Memory Profiler to Run C# Console Application?

Once you click on the Profile Application Option, it will open the below window. From this window, click on the Browse button as shown in the below image.

How to use .NET Memory Profiler to Run C# Console Application?

Once you click on the Browse button, select the EXE i.e. present inside the Bin=>Deubg folder or your project, and click on the Open Folder as shown in the below image.

How to use .NET Memory Profiler to Run C# Console Application?

Once you click on the Open button, it will take you back to the Profile Application window and here, you just need to click on the Start button as shown in the below image.

How to use .NET Memory Profiler to Run C# Console Application?

Once you click on the Start button, it will start executing your console application and you can also observe the generations. You can see most of the objects are in generation 0.

blank

So, the more objects in generation 0 are the better the performance and the more the memory will be utilized in an optimal manner.

How do using a Destructor in a Class 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, and the garbage collector does not have any control over them to clean up the memory.

For example, let’s say you have a class called MyClass in VB6, then you have to expose some function let’s 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 location 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 destroying the object, will go and ask the question to 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 a 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 to Destroy the Unmanaged Resources:

Please have a look at the below code. This is the same example as the previous one except we have added the respective destructors in the class.

using System;
namespace GarbageCollectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i <= 1000000; i++)
            {
                MyClass1 obj1 = new MyClass1();
                MyClass2 obj2 = new MyClass2();
                MyClass3 obj3 = new MyClass3();
            }

            Console.Read();
        }
    }

    public class MyClass1
    {
        ~MyClass1()
        {
            //Here, you need to write the code for
            //Unmanaged resource clean up
        }
    }

    public class MyClass2
    {
        ~MyClass2()
        {            
            //Here, you need to write the code for
            //Unmanaged resource clean up
        }
    }

    public class MyClass3
    {
        ~MyClass3()
        {
            //Here, you need to write the code for
            //Unmanaged resource clean up
        }
    }
}

Now, rebuild the solution. Now, close .NET Memory Profile and follow the same steps to run the console application using this .NET Memory Profiler. This time you will observe that some of the objects are in generation 1 also as shown in the below image.

Example using Destructor to Destroy the Unmanaged Resources

So, if you are writing the clean-up code in your destructor, then you will end up creating 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 Finalized 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 suppresses any kind of destructor and just goes and cleans up the objects. For a better understanding, please have a look at the following image.

IDisposable Interface in C#

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.

Example using Dispose Pattern in C#

Example using Dispose Pattern to Destroy the Unmanaged Object in C#:
using System;
namespace GarbageCollectionDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i <= 1000000; i++)
            {
                MyClass1 obj1 = new MyClass1();
                obj1.Dispose();
                MyClass2 obj2 = new MyClass2();
                obj2.Dispose();
                MyClass3 obj3 = new MyClass3();
                obj3.Dispose();
            }

            Console.Read();
        }
    }

    public class MyClass1 : IDisposable
    {

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects).
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        ~MyClass1()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(false);
        }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
             GC.SuppressFinalize(this);
        }
        #endregion

    }

    public class MyClass2 : IDisposable
    {

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                }
                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        ~MyClass2()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(false);
        }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            GC.SuppressFinalize(this);
        }
        #endregion

    }

    public class MyClass3 : IDisposable
    {
        #region IDisposable Support
        private bool disposedValue = false; 

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                }
                
                disposedValue = true;
            }
        }
        
        ~MyClass3()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}

Now, rebuild the solution. Close .NET Memory Profile and follow the same steps to run the console application using this .NET Memory Profiler. This time you will observe that the objects are created in generation 0 only which improves the performance of your application by utilizing the memory effectively.

Garbage Collection in C#.NET Application with Examples

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.

In the next article, I am going to discuss Access Specifiers in C# with Examples. Here, in this article, I try to explain Garbage Collection in .NET Framework with Examples. I hope you enjoy this Garbage Collection in .NET Framework article and I also hope that now you understood how the garbage collector works in C#.

2 thoughts on “Garbage Collection in C#.NET”

Leave a Reply

Your email address will not be published.