The Uncertainty of Destruction
The goal of the constructor is to reserve resources while the destructors’ goal is to free those resources. Let's see how destructors destroy the instance of an unneeded class.
Aug 15, 2019 • 7 Minute Read
Introduction
The destructor is a concept similar to constructors. While the goal of the constructor is to reserve resources for the object of a specific class. The destructors’ goal is to free those resources. The destructors are strongly coupled with the garbage collector. The garbage collector is the internal mechanism of C# that's sole purpose is to free up those resources which are not needed anymore, but were once reserved by the applications themselves.
Destructors are also called Finalizers.
Garbage Collector
Creating objects in C# means that the CLR(Common Language Runtime) allocates memory from the heap to be used by the object. This is repeated for each consecutive object creation. The limit is theoretically the available memory in the system. This means there is a limit, and we need to consider other applications that may need to use these resources. The memory needs to be released every once in a while, when the application does not need it anymore. The Garbage Collector's sole responsibility is the allocation and reclaimation of this precious resource.
When a new process is created a separate virtual space is assigned to it. This comes from the physical memory and it is used by every other process in the system. Every program is using a virtual space and there is no direct interaction with the physical memory. Garbage Collector is working on this same virtual memory to allocate and reclaim memory.
There are three blocks that exist in the virtual memory:
- Free - Empty space
- Reserved - Already allocated space
- Committed - This is a give-out to physical memory and cannot be used for space allocation
Once you run out of virtual memory an out of memory error occurs.
There is a concept called Generation which reveals to us the inner workings of garbage collection. There are three generations which separate different kinds of objects into distinct categories
Generation categories:
- Generation (Zero) - Holds short-lived objects, temporary objects; the garbage collector is the most frequent in this realm.
- Generation (One) - A buffer between Zero and Two.
- Generation (Three) - Holds the long-lived objects like static and global variables that need to persist for a certain amount of time.
The objects which are not collected at Zero are moved to One, these are called survivors. Then the objects which are not collected at One arrive at Two. This is the final realm an object can reach.
The garbage collector determines if an object is alive by:
- Collecting all handles of an object that are allocated by user code or by CLR.
- Keeping track of static object as they are referenced to some other objects.
- Using stack provided by stack walker and JIT.
Garbage collection can happen in these ways:
- When the virtual memory is running out of space - Automatic.
- When the allocated memory has surpassed the acceptable threshold, threshold allocation is increased - Automatic.
- When the GC.Collect() methods is called explicitly - Manual.
There are two crucial terms we need to be aware of before we can jump into the destructors. The first concept is the managed objects the second is the unmanaged objects.
We are talking about managed object when this object is under the scope of CLR; it's pure .NET code which is managed by runtime. Anything which comes from .NET like classes, basic data structures like strings, integers, etc... are referred to as managed code.
We are talking about unmanaged objects when the object is outside of the control of the .NET libraries and not managed by the CLR. These are objects like COM objects, file and network streams, connections objects, etc...
Releasing resources tied to unmanaged code is more complex than the managed code. The garbage collector is only able to track these resources but it's out of scope to release them.
Ways to clean up unmanaged code:
- Implementing the IDisposable interface and Dispose of method.
- 'using' block
Destructors
Destructors are methods inside a class that's sole purpose is to destroy the instance of that class when they are no longer needed. The destructor is called implicitly by the Garbage Collector of the .NET framework. The programmer only has control over this process when the application implements the GC.Collect() method; otherwise, the framework takes care of this.
In simple terms an object/instance is eligible for destruction when it is no longer reachable.
Crucial properties:
- The structs cannot have finalizers, only classes can.
- Only one finalizer is permitted.
- The finalizers cannot be inherited or overloaded.
- The finalizers cannot be called.
- The finalizers cannot take modifiers or parameters.
An example of the skeleton code:
class Pluralsight
{
// members and methods.
// Destructor or Finalizer
~Pluralsight()
{
Console.WriteLine("The end is near!")
}
}
Any statement you provide in the Finalizer code is going to be called by the Garbage Collector when it's time has come.
If you want you can have the Destructor defined as an expression body definition.
class Pluralsight
{
~Pluralsight() => Console.WriteLine("The end is near!")
}
In .NET Framework applications the finalizers are called when the program exits, but in .NET Core applications this is not the case.
When you are creating a finalizer, it's actually a syntactic sugar. Under the hood, a call is made to the Finalize() function, which is dynamically generated based on your class code, and the finalizers final code looks like below. It is an implicit translation that you need to be aware of.
protected override void Finalize()
{
try
{
// statements from the finalizer code come here.
}
finally
{
base.Finalize()
}
}
This implies that the Finalize method is called recursively for every instance you have from the most-derived to the least-derived.
Conclusion
To me, Garbage Collection was always a mystery filled with questions like "How?", "When?", "Why?" when these things happen. It takes a while until a developer gets used to this internal mechanism which protects the OS from the application itself by cleaning up resources that are not needed anymore. I hope this guide was worth your while and you found what you were looking for!