Garbage Collection in C#

27 augustus 2021 om 10:00 by ParTech Media - Post a comment

Every computer has a volatile memory in the form of Random Access Memory (RAM). This is often used by the applications/operating systems for performing different tasks assigned by the user. Operating systems have the in-built capability to utilize the memory in an optimized way as required.

Now, imagine if developers created a myriad of applications, hosted them in the computer/server, and allowed them to be accessed publicly or privately. In this case, the applications that are being created also consume the memory for performing its task.

One such example is the object of the class that holds random memory for getting things done. The applications consume memory and when that happens, the task of removing the unused memory comes into the picture. In other words, when the work of the object is over within the code, then the memory occupied by the object is not useful and can actually be cleaned up.

Application developers need to develop the business logic of the application and along with that, a memory clean-up logic - doesn’t it sound tedious? Also, there is a possibility that it might not be cleaned effectively and efficiently which results in hampering the performance of the application by consuming a significant chunk of system memory.

Luckily, for all the .Net developers, C# comes with an in-built Garbage Collector to help in this process. Here’s everything you need to know about it.

Table of contents

  1. What is a Garbage Collector?
  2. Managed heap generations
  3. Understanding generations with examples
  4. Conclusion

What is a Garbage Collector?

A Garbage Collector (GC) serves as an automatic memory manager in the .Net Common Language Runtime (CLR). It takes care of memory management by assigning and releasing memory that is consumed in an application. In simple words, the garbage collector takes care of the memory management for the managed code.

The memory for an object gets assigned immediately after an object is declared with the ‘new’ keyword. However, the release of the memory does not happen immediately after the tasks of the object are complete. Rather it has to satisfy one of the three below conditions -

  1. When the system memory is low, i.e when the OS notifies that the memory is low
  2. Memory allocated to the object surpasses the threshold value. The threshold keeps updating as the process runs
  3. When the GC.Collect method is executed from the code. Explicit calling is not generally required as the GC runs continuously to free up memory. Under special cases, explicit calling is performed.

The memory of the objects that are being consumed in an application is stored in a managed heap. GC reserves memory for the managed applications and has limited work when the allocated objects in the managed heap are fewer.

Once the scope of the object gets over in the code, they become dead objects. GC reclaims the memory of these objects from the managed heap and makes the heap size smaller. Typically, the GC is performance-optimized based on the logic of heap generations.

Managed heap generations

Based on the scope of the variable, the generations of the managed heap are classified. Usually, short-lived objects can be cleaned quickly and the object which is required for a longer period cannot be cleaned until a particular state is reached. To address this, the objects are classified into different generations based on their longevity. There are three generations viz. generation - 0, generation -1, and generation - 2. Where generation - 0 holds the short-lived objects and generation -2 holds the long-lived objects.

In this way, the managed heap is divided into three sections. This makes it easier and quicker for the GC to clean up a section than cleaning up the entire heap. Let’s understand the three sections in a more detailed manner.

Generation - 0 - It holds the newly created objects, temporary variables and it is the youngest generation. However, if the memory size of the object is large, then it goes to generation - 2 directly. Garbage collection happens more frequently in this generation. GC cleans memory by default in generation - 0, to free up space for application usage.

Generation - 1 - This serves as a buffer between short-lived and long-lived objects. When garbage collection happens in generation - 0, the surviving objects get promoted to generation -1, as their scope is longer. If the GC clean-up is not sufficient in Generation - 0, then GC tries to reclaim the memory from generation - 1. Similarly, objects that survive the clean-up get promoted to generation - 2.

Generation -2 - This holds the long-lived objects of the application. An example would be static objects that get initialized during the start of the application. Objects that survive a clean-up in this generation, remain here until the object scope is not valid in future collections.

Understanding generations with examples

Below is a sample code that helps in understanding the concept of Garbage Collection.

Note: Comments have been provided for better understanding.

using System;
namespace PARTECH_GC
{
  class Program
  {
​    static void Main(string[] args)
​    {
​      Console.WriteLine($"Maximum available garbage collection generations - { GC.MaxGeneration }");// Checking the maximum available generation for GC.

​      var program = new Program(); //Creating an new object

​      Console.WriteLine($"program object generation - { GC.GetGeneration(program) }"); //Checking the generation that object belongs to. 

​      GC.Collect(0); //Explicitly calling GC to free up memory from generation 0. 

​      Console.WriteLine($"Number of collection in generation - 0 - { GC.CollectionCount(0) }"); //Checking the number of times the GC has run in generation - 0.

​      Console.ReadLine();
​    }
  }
}

Below is the output of the above program once executed.

Conclusion

Garbage collector helps in the memory management of C# based managed code applications. It does this effectively by handling it in the managed heap. Use this in your code to improve the performance of your application by achieving effective memory management.