.NET Documentation

Garbage Collection in .NET

Garbage collection (GC) is an automatic memory management feature in the .NET runtime. It reclaims memory occupied by objects that are no longer in use by the application. This frees developers from manually allocating and deallocating memory, significantly reducing the risk of memory leaks and other common memory-related bugs.

How Garbage Collection Works

The .NET GC operates on a generational, mark-and-sweep algorithm. Here's a simplified overview:

Generational Garbage Collection

The .NET GC employs a generational approach to optimize performance. The managed heap is divided into generations:

The GC performs collections more frequently on younger generations (Generation 0) because it's statistically more likely for objects in these generations to be garbage. This reduces the overhead of scanning the entire heap for every collection.

Configuring Garbage Collection

While the GC is largely automatic, you can influence its behavior using configuration settings and specific API calls.

GC Modes:

Server GC is the default for ASP.NET applications and services, while Workstation GC is the default for desktop applications.

Manual Triggering:

In certain scenarios, you might want to explicitly trigger a garbage collection. Use the GC.Collect() method with caution, as it can introduce performance overhead if used inappropriately.

C#

// Request a full, blocking garbage collection
GC.Collect();

// Request a generation-specific collection
GC.Collect(0);
GC.Collect(1);
GC.Collect(2);
                    

Finalization and Dispose Pattern

For objects that manage unmanaged resources (like file handles, network connections, or database connections), you need to ensure these resources are released when the object is no longer needed. This is typically handled using the Dispose pattern.

C#

public class MyResourceHolder : IDisposable
{
    private IntPtr unmanagedHandle;

    public MyResourceHolder()
    {
        // Allocate unmanaged resources
        unmanagedHandle = AllocateUnmanagedMemory();
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); // Prevent finalizer from running if Dispose is called
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            // Release managed resources here if any
        }

        // Release unmanaged resources
        if (unmanagedHandle != IntPtr.Zero)
        {
            ReleaseUnmanagedMemory(unmanagedHandle);
            unmanagedHandle = IntPtr.Zero;
        }
    }

    ~MyResourceHolder()
    {
        // Finalizer: Only release unmanaged resources
        Dispose(false);
    }

    // ... other methods ...
}

// Using the resource
using (var holder = new MyResourceHolder())
{
    // Use the object
} // Dispose() is automatically called here
                    

Best Practices

Note: Modern .NET versions have highly optimized GC algorithms. In most cases, relying on the default GC behavior is sufficient. Only delve into advanced configuration or manual collection if you have identified specific performance bottlenecks related to memory management.