Demystify Garbage Collection in C#: Part 2

In previous article we were talking about concept of Generation. When a fresh object get create it creates in generation 0 (Unless it is very large object, in .NET more than 85,000 bytes objects are consider as large object).

Welcome to the Demystify Garbage Collection series of articles. Before reading this article I recommend you look at the previous part; see:

Demystify Garbage Collection in C#: Part-1

In the previous article we were talking about the concept of Generation. When a fresh object is created it is created in generation 0 (unless it is a very large object, in .NET more than 85,000 bytes objects are consider to be large objects). Now there are two conditions that need to be satisfied; they are:

  1. The object is not garbage, in other words there is still a reference for it in the application.
  2. GC.Collect() has run.

The object will be promoted to a higher generation, in other words here generation 1. And for the same condition it will be promoted to the next generation, in other words generation 2. Now question is, how many generations are there? N? What is the maximum value for N? The maximum value for N is 2, yes generation 2 is the maximum generation. (Those who want to be confirm from Microsoft's guys). In the following example we will see how an object is promoted from a lower generation to a higher generation. Have a look at the code below.

using System;

using System.Collections;

using System.Globalization;

using System.Data.SqlClient;

using System.Data;

 

namespace Test1

{

    public class Garbage

    {

        public String name = "";

        public Garbage()

        {

 

            Console.WriteLine("Reserve memory");

        }

        ~Garbage()

        {

            Console.WriteLine("Free memory");

        }

 

    }

    class Program

    {

        static void Main(string[] args)

        {

            {   //Genearte scope here

                Garbage g = new Garbage();

                g.name = "Soutav";

                Console.WriteLine("First Created To:- "GC.GetGeneration(g));

               

                GC.Collect();

                Console.WriteLine("Promoted To:- "GC.GetGeneration(g));

               

                GC.Collect();

                Console.WriteLine("Promoted To:- " + GC.GetGeneration(g));

 

                GC.Collect();

                Console.WriteLine("Promoted To:- " + GC.GetGeneration(g));

 

            }

            Console.ReadLine();

        }

    }

}

GarbageCollection1.jpg

The object is intially created in generation 0. Then when GC.Collect() runs it cannot collect the object and promotes it to generation1. Again GC.Collect() tries to collect the object from generation1 and when it cannot, it promotes the object to the higher generation. And in this example we are seeing the maximum generation is 2.

Now, again the question is, "Is it good that an object is promoted to a higher generation?" The answer is no.

If you see, the maximum objects of your application are created in a lower generation (in other words generation 0) then you can consider it as a best practice.

So, one concept is clear."Maximum object should die within generation 0". In the next section we will try to discuss various overloaded forms of the Collect() function.

GC.Collect() with no argument.

It's very easy to get information about various overloaded methods of the Collect() function. As description help from VisualStudio we are clearly seeing. The simple GC.Collect() collects garbage from all generations.

GarbageCollection2.jpg

In the example below we will prove the concept. Have a look.
 

using System;

using System.Collections;

using System.Globalization;

using System.Data.SqlClient;

using System.Data;

 

namespace Test1

{

    public class Garbage

    {

        public String name = "";

        public Garbage()

        {

 

            Console.WriteLine("Reserve memory");

        }

        ~Garbage()

        {

            Console.WriteLine("At last Garbage is Collected");

        }

 

    }

    class Program

    {

        static void Main(string[] args)

        {

            {   //Genearte scope here

                Garbage g = new Garbage();

                g.name = "Soutav";

                Console.WriteLine("First Created To:- "GC.GetGeneration(g));

               

                GC.Collect();

                Console.WriteLine("Promoted To:- "GC.GetGeneration(g));

               

                GC.Collect();

                Console.WriteLine("Promoted To:- " + GC.GetGeneration(g));

                g = null;   //Set null in Generation - 2

                GC.Collect();

               

            }

            Console.ReadLine();

        }

    }

}

Here is the output screen:

GarbageCollection3.jpg

At first we are promoting the object to a higher generation and then making the object garbage. Then
we are invoking GC.Collect() to collect all garbage from all generations. And at last the object is getting collected from generation 2. Hence we can prove that GC.collect() goes through all generations. (This is the best I have done wit my experience, if you have another implementation, please share with us).

GC.Collect() with generation as argument

The second overloaded function takes generation as an argument. And the documentation says it will take one integer argument indicating a specific generation.

GarbageCollection4.jpg

And the collection will perform 0 to that generation. For example if we specify 1 as an argument then garbage collection will be performed in the 0 and 1 generations. Here we will prove that concept.
 

using System;

using System.Collections;

using System.Globalization;

using System.Data.SqlClient;

using System.Data;

 

namespace Test1

{

    public class Garbage

    {

        public String name = "";

        public Garbage()

        {

 

            Console.WriteLine("Reserve memory");

        }

        ~Garbage()

        {

            Console.WriteLine("At last Garbage is Collected");

        }

 

    }

    class Program

    {

        static void Main(string[] args)

        {

            {   //Genearte scope here

                Garbage g = new Garbage();

                Garbage g1 = new Garbage();

                g.name = "Soutav";

                Console.WriteLine("g is Created:- " + GC.GetGeneration(g));

                Console.WriteLine("g1 is Created:- " + GC.GetGeneration(g1));

 

                GC.Collect();

                Console.WriteLine("g is promoted To:- " + GC.GetGeneration(g));

                Console.WriteLine("g1 is promoted To:- " + GC.GetGeneration(g1));

                g = null;

                g1 = null;

 

                Garbage g2 = new Garbage();

                Console.WriteLine("g2 is Created:- " + GC.GetGeneration(g2));

                g2 = null;

                GC.Collect(0); //Collect only 0th generation

 

            }

            Console.ReadLine();

        }

    }

}

GarbageCollection5.jpg

Here the g2 object is being created in generation 0 and before that we have promoted the g and g1 objects to generation 1. Now we are performing clean up in generation 0 by specifying GC.Collect(0). In the example below we will specify 1 as an argument and we will see the changes.

Here is the modified Main() function of the above program.
 

static void Main(string[] args)

{

    {   //Genearte scope here

        Garbage g = new Garbage();

        Garbage g1 = new Garbage();

        g.name = "Soutav";

        Console.WriteLine("g is Created:- " + GC.GetGeneration(g));

        Console.WriteLine("g1 is Created:- " + GC.GetGeneration(g1));

 

        GC.Collect();

        Console.WriteLine("g is promoted To:- " + GC.GetGeneration(g));

        Console.WriteLine("g1 is promoted To:- " + GC.GetGeneration(g1));

        g = null;

        g1 = null;

 

        Garbage g2 = new Garbage();

        Console.WriteLine("g2 is Created:- " + GC.GetGeneration(g2));

        g2 = null;

        GC.Collect(1);

 

    }

    Console.ReadLine();

}

The output is here:

GarbageCollection6.jpg

Now we can see three destructors have been executed. In other words garbage collection was performed in both generation 0 and generation 1.

GC.Collect() with generation and collection mode

This overloaded method takes two arguments. The first one is a generation and the second one is the Collection mode. As we know, generation is the maximum generation value and from 0 to that generation the operation will be performed. And here the second argument specifies the collection type. There are three collection types as in the following:

GarbageCollection7.jpg

  1. Forced: It will force the garbage collector to perform the operation immediately.

  2. Default: In the current version if C# .NET Framework (4.5) Default and Forced are the same.

  3. Optimal: Allow the garbage collector to decide whether to to take action this time.

Conclusion

If you are still reading then I hope that you have enjoyed this article. In the future of this series we will discuss this topic more.