Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Garbage Collection, Finalizers, and Disposal in C#

Tech 1

Memory Allocation: Stack vs. Heap

Data is stored in memory, which is divided into Stack and Heap segments.

Stack Memory Heap Memory
Fast, efficient access Complex structure
Limited size and types Stores objects
Holds simple data Stores reference types
Stores primitive and value types

Consider this example:

var customer = new Customer
{
    Id = 123,
    Name = "Jack",
    Address = "Zhuhai"
};

In heap memory, the object's data is stored:

| Address (e.g., #1000) | Id: 123 | Name: "Jack" | Address: "Zhuhai" |

On the stack, only a reference (pointer) is stored: customerRef = 1000. If the stack reference is deleted first, the corresponding heap data becomes unreachable and cannot be freed, leading to a memory leak. Languages like C# and Java introduce automatic garbage collection (GC) to manage this, allowing developers to focus on object logic. An object becomes eligible for collection when its reference count drops to zero.

How the Garbage Collector Operates

Garbage collection is computationally expensive, requiring traversal of object graphs. To improve efficiency, a Generational model is used, grouping objects into three categories:

Generation 0 Generation 1 Generation 2
Short-lived objects Mid-term objects Long-lived objects
Checked every GC cycle Checked less frequently Checked occasionally

When memory pressure is high, GC may collect objects from all generations.

GC Responsibilities Extend Beyond Simple Cleanup

  • Identifies and marks dead objects in the heap.
  • Compacts memory to eliminate fragmentation, improving allocation and access speed. Note: Very large memory segments are not compacted.

GC Runs on a Dedicated Thread

  • Executes on a sepraate background thread.
  • Each collection cycle consumes computational resources.
  • The system is designed to minimize collection frequency and maximize efficiency.

Finalizers and the IDisposable Pattern

A finalizer (historically called a destructor) is used to perform necessary cleanup before an object is reclaimed by the garbage collector. In most cases, writing finalizers is unnecessary if unmanaged resources are wrapped using System.Runtime.InteropServices.SafeHandle or a derived class.

Avoid using finalizers unless absolutely required, as they incur performance overhead.

Example:

public class ResourceHolder
{
    public ResourceHolder()
    {
        Console.WriteLine("ResourceHolder instance created.");
    }
    ~ResourceHolder() // Finalizer
    {
        Console.WriteLine("ResourceHolder instance finalized.");
    }
}
class Program
{
    static void Main(string[] args)
    {
        var holder = new ResourceHolder();
        Console.WriteLine("End of Main method.");
        // The object is still in scope here, so the finalizer likely won't run yet.
    }
}

Running this code typically will not display the finalizer's output. This is because the object remains reachable (in scope) until the Main method exits. The garbage collector runs at an indeterminate time after the object becomes unreachable. To potentially observe the finalizer running, limit the object's scope:

class Program
{
    static void CreateAndRelease()
    {
        var holder = new ResourceHolder();
    } // 'holder' goes out of scope here
    static void Main(string[] args)
    {
        CreateAndRelease();
        // Suggest garbage collection (for demonstration only).
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("End of Main method.");
    }
}

For deterministic cleanup of unmanaged resources (like file handles or database connections), implement the IDisposable pattern:

public class ManagedResource : IDisposable
{
    private bool _disposed = false;
    private System.IO.Stream _fileStream; // Example unmanaged resource
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Finalizer not needed if already disposed
    }
    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                // Release managed resources
                _fileStream?.Close();
            }
            // Release unmanaged resources here
            _disposed = true;
        }
    }
    ~ManagedResource() // Finalizer as a safety net
    {
        Dispose(false);
    }
}
// Usage
using (var resource = new ManagedResource())
{
    // Work with resource
} // Dispose() is called automatically here
Tags: C#

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.