Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Garbage Collector Behavior for Destructor

I have a simple class that is defined as below.

public class Person
{
    public Person()
    {

    }

    public override string ToString()
    {
        return "I Still Exist!";
    }

    ~Person()
    {
        p = this;

    }
    public static Person p;
}

In Main method

    public static void Main(string[] args)
    {
        var x = new Person();
        x = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(Person.p == null);

    }

Is garbage collector supposed to main reference for Person.p and when exactly will the destructor be called?

like image 714
Parimal Raj Avatar asked Oct 14 '19 12:10

Parimal Raj


People also ask

Does garbage collector call destructor?

What is Garbage Collector? Garbage collector looks for an instance/object which is not required anymore and treats these objects/instances as ready to destroy. Then, it makes a call to the destructor to release the memory and deallocate the resources.

What is garbage collector function?

In the common language runtime (CLR), the garbage collector (GC) serves as an automatic memory manager. The garbage collector manages the allocation and release of memory for an application. For developers working with managed code, this means that you don't have to write code to perform memory management tasks.

Is there a destructor for Java *?

Because Java is a garbage collected language you cannot predict when (or even if) an object will be destroyed. Hence there is no direct equivalent of a destructor.

What is the difference between constructor and garbage collector?

The garbage collector is a part of the . NET environment that keeps track of objects and makes sure that objects are removed from memory when they are no longer needed. A destructor is a part of a class design. It's the opposite of a constructor.


1 Answers

The thing you are missing here is that the compiler is extending the lifetime of your x variable until the end of the method in which it is defined - that's just something the compiler does - but it only does it for a DEBUG build.

If you change the code so that the variable is defined in a separate method, it will work as you expect.

The output of the following code is:

False
True

And the code:

using System;

namespace ConsoleApp1
{
    class Finalizable
    {
        ~Finalizable()
        {
            _extendMyLifetime = this;
        }

        public static bool LifetimeExtended => _extendMyLifetime != null;

        static Finalizable _extendMyLifetime;
    }

    class Program
    {
        public static void Main()
        {
            test();

            Console.WriteLine(Finalizable.LifetimeExtended); // False.

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(Finalizable.LifetimeExtended); // True.
        }

        static void test()
        {
            new Finalizable();
        }
    }
}

So basically your understanding was correct, but you didn't know that the sneaky compiler was going to keep your variable alive until after you called GC.Collect() - even if you explicitly set it to null!

As I noted above, this only happens for a DEBUG build - presumably so you can inspect the values for local variables while debugging to the end of the method (but that's just a guess!).

The original code DOES work as expected for a release build - so the following code outputs false, true for a RELEASE build and false, false for a DEBUG build:

using System;

namespace ConsoleApp1
{
    class Finalizable
    {
        ~Finalizable()
        {
            _extendMyLifetime = this;
        }

        public static bool LifetimeExtended => _extendMyLifetime != null;

        static Finalizable _extendMyLifetime;
    }

    class Program
    {
        public static void Main()
        {
            new Finalizable();

            Console.WriteLine(Finalizable.LifetimeExtended); // False.

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(Finalizable.LifetimeExtended); // True iff RELEASE build.
        }
    }
}

As an addendum: Note that if you do something in the finalizer for a class that causes a reference to the object being finalized to be reachable from a program root, then that object will NOT be garbage-collected unless and until that object is no longer referenced.

In other words, you can give an object a "stay of execution" via the finalizer. This is generally held to be a bad design, though!

For example, in the code above, where we do _extendMyLifetime = this in the finalizer, we are creating a new reference to the object, so it will not now be garbage-collected until _extendMyLifetime (and any other reference) no longer references it.

like image 185
Matthew Watson Avatar answered Oct 01 '22 13:10

Matthew Watson