Programmingempire
In this post on How the .NET Framework Performs Garbage Collection, I will explain the process of Garbage Collection in the .NET Framework. Evidently, memory management is a crucial aspect of any software application since it determines the performance 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
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
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
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.