.NET CLR Memory Management
Overview
The Common Language Runtime (CLR) provides an automated memory-management system that relieves developers from most of the complexities associated with manual memory allocation and deallocation. This system is primarily driven by the Garbage Collector (GC), which periodically reclaims memory occupied by objects that are no longer reachable.
Garbage Collector
The GC is a generational, mark-and-sweep collector optimized for short-lived objects. It operates in several phases:
- Mark Phase: Identifies live objects by traversing object graphs starting from roots (stack, static fields, GC handles).
- Sweep Phase: Reclaims memory occupied by unmarked objects.
- Compaction: (optional) Relocates live objects to eliminate fragmentation.
The GC runs in the background and can be triggered explicitly via GC.Collect(), although this is generally discouraged.
using System;
class Demo {
static void Main() {
var largeArray = new byte[10_000_000];
Console.WriteLine($"Allocated {largeArray.Length} bytes.");
// Force a collection (usually not needed)
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
Generations
The CLR categorizes objects into three generations based on their longevity:
- Generation 0: Short‑lived objects. Collected most frequently.
- Generation 1: Objects that survived one GC cycle.
- Generation 2: Long‑lived objects. Collected less often.
Promotion to a higher generation occurs when an object survives a collection. The GC.GetGeneration(object) method can be used to inspect an object's generation.
Large Object Heap (LOH)
Objects larger than 85,000 bytes are allocated on the Large Object Heap. The LOH is not compacted by default, which can lead to fragmentation. .NET 4.5 introduced GCSettings.LargeObjectHeapCompactionMode to enable incremental compaction.
GCSettings.LargeObjectHeapCompactionMode =
System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
Finalization
When an object defines a finalizer (~ClassName()), the GC adds the object to the finalization queue after it becomes unreachable. Finalizers run on a dedicated thread and can delay memory reclamation. Prefer implementing IDisposable and using using blocks over finalizers.
Common Memory Leaks
Even with GC, certain patterns can cause memory leaks:
- Static event handlers that retain references to disposable objects.
- Unreleased unmanaged resources without proper
Disposeimplementation. - Large objects kept alive unintentionally via caches.
Tools such as dotMemory and the Visual Studio Diagnostic Tools help detect leaks.
Best Practices
- Prefer
usingstatements forIDisposableobjects. - Avoid retaining large objects in long‑lived collections.
- Do not call
GC.Collect()in production code. - Use
WeakReferencefor caches where objects can be reclaimed. - Monitor memory usage with profiling tools during development.