How the .NET Framework Performs Garbage Collection

In this post on How the .NET Framework Performs Garbage Collection, I will explain the process of Garbage Collection in the .NET Framework. Evidently, the memory management is a crucial aspect of any software application since it determines the performmance of an application.

Furthermore, an online application must employ a sophisticated mechanism for memory management since it can determine the number of online users of the application. Hence, the success of a web application is largely dependent on the memory management techniques it employs.

Memory Management in .NET Framework

Basically, Objects are allocated memory dynamically. In particular, they are allocated memory from the managed heap. Specifically, a .NET application doesn’t require the programmer to perform memory management functions manually. Hence, the programmer is not bothered about taking care of memory requirement and releasing it. Further, CLR maintains a memory area called a managed heap, and objects are allocated memory from this specific region.

It is the Garbage Collector module of CLR (Common Language Runtime) that runs in the background and determines when there is a need to free some memory and what are those objects should be destroyed first.

Generations of Objects

In short, the garbage collector maintains three generations of objects. We call these generations generation 0, generation 1, and generation 2. Basically, generation 0 holds the newest objects which are collected first if they are no longer in use. However, the surviving objects are promoted to generation 1, which contains somewhat older objects. Consequently, the oldest objects reside in generation 2.

GC Class in .NET Framework

In fact, the .NET Framework provides a class called GC that offers several properties and methods related to garbage collection. Specifically, the GC class is a static class. Although Garbage Collector runs automatically in the background, still we can enforce it by using the Collect() method of the GC class.

Properties of the GC Class

MaxGeneration

Basically, it is a static readonly property that indicates how many generations of objects the system supports currently.

Methods of the GC Class

Although the GC class contains several methods. However, for the purpose of illustration, I describe some frequently used ones below. Specifically, Collect(), GetGeneration(), and GetTotalMemory() are described here.

Collect()

Sometimes we want to reclaim the maximum amount of memory meaning that all the memory except that is currently being referenced. For this purpose, we can use the Collect() methods. Basically, this method reclaims the memory from all unreferenced objects irrespective of their generation.

Furthermore, the GC class has several overloads of the Collect() method. Specifically, the Collect() method performs the garbage collection of all generations, whereas the Collect(Int32) method reclaims memory starting from Generation 0 to the generation specified by the parameter.

GetGeneration()

Evidently, an object may belong to any of the generations 0, 1, or 2, and the GetGeneration() method fetches the current generation that an object belongs to.

GetTotalMemory()

In order to find out how much memory is currently in use, we can call the GetTotalMemory() method. In addition, we can make the method return immediately or wait for the garbage collection before returning. For this purpose, we can pass a boolean parameter to the function.

An Illustration of Garbage Collection

Now that, we have the GC class that we can use to know about the generations of objects and total memory, consider the following program that creates some garbage of objects. Once, the garbage is created, we invoke the Collect() method to perform the garbage collection. Also, the program displays the current generation of objects and total memory allocated.

Firstly, define the following class that will help us in creating the garbage of objects.

class A
    {
        decimal a = 1.2M;
        decimal b = 2.4M;
        const double pi = 3.14;
        readonly long i;
        public A(long i)
        {
            this.i = i;
        }
    }

In the meantime, we create several objects of the above class in the main() method and call various methods of the GC class. The complete code is given below.

using System;

namespace GCDemo2
{
    class Program
    {
        static void Main(string[] args)
        {
            Program ob = new Program();

            Console.WriteLine($"The Maximum generation that the system supports is {GC.MaxGeneration}");

            ob.CreateSomeObjects();

            // Display the generation where the ob is stored
            Console.WriteLine($"Generation that stores ob is {GC.GetGeneration(ob)}");
            Console.WriteLine($"Approximate Memory Allocation (in bytes) {GC.GetTotalMemory(false)}");

            Console.WriteLine("Collecting objects upto generation 0 only...");
            GC.Collect(0);

            Console.WriteLine($"Now the object ob is stored in Generation {GC.GetGeneration(ob)}");
            Console.WriteLine($"Now the total allocated memory is (in bytes) {GC.GetTotalMemory(false)}");

            Console.WriteLine("Collecting objects upto generation 1...");
            GC.Collect(1);

            Console.WriteLine($"Now the object ob is stored in Generation {GC.GetGeneration(ob)}");
            Console.WriteLine($"Now the total allocated memory is (in bytes) {GC.GetTotalMemory(false)}");

            Console.WriteLine("Collecting objects upto generation 2...");
            GC.Collect(2);

            Console.WriteLine($"Now the object ob is stored in Generation {GC.GetGeneration(ob)}");
            Console.WriteLine($"Now the total allocated memory is (in bytes) {GC.GetTotalMemory(false)}");
            Console.ReadLine();
        }
        void CreateSomeObjects()
        {
            A r;

            for (long i = 0; i < 1000; i++)
            {
                // Creating some objects
                r = new A(i);
            }
        }
    }
    class A
    {
        decimal a = 1.2M;
        decimal b = 2.4M;
        const double pi = 3.14;
        readonly long i;
        public A(long i)
        {
            this.i = i;
        }
    }
}

Output

Demonstration of Garbage Collection
Demonstration of Garbage Collection

After that, we increase some garbage. Therefore, we make following changes in the code.

void CreateSomeObjects()
{
     A r;

     for (long i = 0; i < 1000000; i++)
     {
        // Creating some objects
        r = new A(i);
     }
}

Output

Object Generations after Increasing Number of Objects
Object Generations after Increasing Number of Objects

Since, the number of objects is now very large, the object ob promotes to the generation 0. Further increasing the allocation of objects will cause the ob to promote to generation 2 as shown below.

void CreateSomeObjects()
        {
            A r;

            for (long i = 0; i < 1000000000; i++)
            {
                // Creating some objects
                r = new A(i);
            }
        }

Output

Object Generations and Garbage Collection
Object Generations and Garbage Collection

Summary

In this article on How the .NET Framework Performs Garbage Collection, I have explained how a .NET application manages its memory. Further, the usage of the GC class is explained along with its properties and methods. Finally, the program code demonstrates how the Collect() method performs garbage collection.


Related Topics

Just-In-Time (JIT) Compiler

Assemblies in .NET

Compilation and Execution Plan of .NET

Common Language Specification (CLS)

Getting Familiar with the Common Type System

Understanding the Architecture of .NET Framework

Leave a Comment

Your email address will not be published. Required fields are marked *