← Back to Tutorials Chapter 4

Memory Management

Stack vs Heap Allocation

The stack stores value types and reference pointers with a fixed lifetime tied to the method scope. The heap stores reference type instances and dynamically sized data. Stack allocations are faster because they use a simple pointer adjustment; heap allocations require garbage collection overhead.

int value = 42;          // Stored on the stack
object obj = new object(); // Reference on the stack, object on the heap

Garbage Collection

The .NET garbage collector (GC) manages heap memory automatically. It runs in generations:

GC Modes

The GC can run in workstation mode (default) or server mode (optimized for multi-processor throughput). Server GC uses separate heaps per core to reduce contention.

Forcing a Collection

GC.Collect();       // Forces a blocking collection
GC.WaitForPendingFinalizers(); // Waits for finalizers to complete
Avoid calling GC.Collect() in production code. The runtime optimizes collection timing based on memory pressure and allocation patterns.

IDisposable and the using Statement

Unmanaged resources (file handles, database connections, network sockets) must be released explicitly. Implement IDisposable to clean them up:

using var file = new StreamReader("data.txt");
string content = file.ReadToEnd();
// file.Close() is called automatically at the end of the scope

The using statement ensures Dispose() is called even if an exception is thrown.

Finalizers

A finalizer (destructor) is called by the GC before reclaiming memory. Finalizers should only release unmanaged resources:

class ResourceHolder
{
    ~ResourceHolder() => Cleanup();
    private void Cleanup() { /* release resources */ }
}

Weak References

Weak references let you hold a reference to an object without preventing its collection. Useful for caches:

WeakReference<MyClass> weak = new(new MyClass());
if (weak.TryGetTarget(out var obj))
{
    Console.WriteLine("Object still alive");
}

Memory Leaks Prevention

Common leak sources include: event handlers that are never unsubscribed, static collections holding object references, and large objects that pin memory. Use memory profiling tools like dotMemory or PerfView to diagnose leaks.

Span<T> and Memory<T>

These stack-allocatable types enable high-performance memory access without allocations:

Span<int> span = stackalloc int[] { 1, 2, 3, 4, 5 };
span[0] = 10;  // Modifies in-place, no heap allocation
.NET Core memory management
Exercise: Create a console app that allocates a large number of objects (e.g., 1 million strings), force a GC collection, and measure memory usage before and after using GC.GetTotalMemory().