.NET GC glossary
Terms found in .NET GC traces (EventPipe .nettrace files). Each entry says what the term means, what it implies for performance and which setting acts on it.
Official documentation
- Fundamentals of garbage collection (Microsoft Learn)
- Workstation and server garbage collection (Microsoft Learn)
- Garbage collector configuration settings (Microsoft Learn)
- Latency modes (Microsoft Learn)
Background GC
Concurrent collection of generation 2, which lets gen 0 and gen 1 collections run in the meantime.
A blocking gen 2 collection stops all managed threads for a long time. With background GC (on by default, setting System.GC.Concurrent), a dedicated thread marks gen 2 concurrently with the application. If gen 0 fills up in the meantime, a foreground collection of the ephemeral generations briefly suspends the threads, runs, then lets the background collection continue.
Only a gen 2 collection that cannot be a background one, for example when it is induced as blocking or when memory is very low, is a long pause. Background GC reduces pause times at the cost of some CPU and memory. GC Doctor labels .NET collections as background or blocking, which explains why a gen 2 collection did or did not cause a long pause.
Official documentation: Background garbage collection (Microsoft Learn)
Compacting vs sweeping
After marking, the GC either compacts survivors together or only records the free gaps (sweep). It chooses for each collection.
Compaction moves live objects together, which removes fragmentation and makes allocation a pointer bump, but costs copying time and the update of references. Sweeping is cheaper but leaves holes, which go on a free list and are reused.
The GC normally compacts the ephemeral generations and decides for gen 2 from the fragmentation; the LOH is swept by default. A gen 2 that is not compacting while the heap keeps growing suggests fragmentation. GCSettings.LargeObjectHeapCompactionMode lets you request a LOH compaction at the next blocking gen 2.
Official documentation: Fundamentals of garbage collection (Microsoft Learn)
Finalization
A finalizer is a method the runtime calls before reclaiming an object. Objects with finalizers survive at least one extra collection.
When an object with a finalizer becomes unreachable, the GC cannot free it right away: it moves it to the finalization queue, a dedicated thread runs the finalizer, and only a later collection reclaims the memory. Such objects are therefore promoted to older generations, and a slow finalizer or a long queue keeps memory alive.
Prefer IDisposable with SafeHandle, call Dispose (using statements) and call GC.SuppressFinalize to take the object off the queue. A growing finalization queue, visible in the .NET diagnostic counters, often explains the growth of gen 2.
Official documentation: Cleaning up unmanaged resources (Microsoft Learn)
GC suspension
Before most collections the runtime must suspend all managed threads. The time this takes is the suspension time.
Managed threads are stopped at safe points in their code. Threads in native code are marked so that they cannot return to managed code during the collection. A thread that does not reach a safe point quickly (a long tight loop, a long operation) delays the whole collection while the others wait.
In .NET traces, suspension time is reported separately from the collection time. GC Doctor charts it so that a long pause can be attributed to the collector or to the wait before it. Long suspensions often come from hot loops, thread pool starvation or an overloaded machine.
Gen 0 budget
The amount of new allocation after which a gen 0 collection starts. The runtime tunes it dynamically.
The budget depends on the processor cache size, on the survival rate of previous collections and on the GC mode (server GC uses larger budgets). A high survival rate raises the budget so that fewer, larger collections run; a low one lowers it.
The budget is why gen 0 collections occur at a regular allocation volume, and why the allocation rate is the first thing to look at when collections are too frequent. The allocation tick events of a trace (one about every 100 KB allocated) feed the allocation charts of GC Doctor.
Generations (gen 0, 1, 2)
The .NET managed heap is divided by age: gen 0 for new objects, gen 1 as a buffer, gen 2 for long-lived objects.
A collection of generation N also collects all younger generations: a gen 1 collection includes gen 0, and a gen 2 collection, called full or blocking, covers the whole heap including the large object heap. Survivors of gen 0 are promoted to gen 1, survivors of gen 1 to gen 2. Gen 0 and gen 1 are the ephemeral generations; they are small and quick to collect.
A healthy application shows many gen 0 collections, fewer gen 1 and rare gen 2. A high share of gen 2 collections, or gen 1 collections that promote a lot, means objects survive longer than they should: caches, bursts of allocation, objects kept alive by finalizers. The GC events of a .nettrace give the generation and the reason of each collection; GC Doctor charts them per generation.
Official documentation: Fundamentals of garbage collection (Microsoft Learn)
Large object heap (LOH)
A separate area for objects of 85,000 bytes or more. It is collected only together with generation 2.
Large objects are not allocated in gen 0, because copying them would be expensive. The LOH is logically part of gen 2: it is collected only when a gen 2 collection runs, and it is normally swept, not compacted (free blocks are reused and adjacent ones merged). Compaction can be requested with GCSettings.LargeObjectHeapCompactionMode. Temporary large arrays (buffers, large strings, growing lists) therefore fill the LOH quickly, trigger expensive gen 2 collections and can fragment it.
Remedies: reuse buffers (ArrayPool), avoid collections that grow by doubling, stream data instead of loading it whole, and adjust the threshold with the GCLOHThreshold setting if needed. It is the .NET counterpart of humongous objects in G1.
Official documentation: The large object heap on Windows (Microsoft Learn)
Pinned object heap (POH)
A heap introduced in .NET 5 for objects that must never move, allocated with GC.AllocateArray and the pinned option.
Objects passed to native code or used for I/O must keep a fixed address while it uses them. Pinning an object in the ordinary heap prevents the compactor from moving it and can fragment gen 0. The POH is a separate area where such objects live for their whole life, out of the way of the normal heap.
Only arrays of types without references can be allocated there (GC.AllocateArray and GC.AllocateUninitializedArray with pinned set to true). Like the LOH, the POH is collected with gen 2 and is not compacted.
Pinning
Fixing an object at its address so that the GC cannot move it, as native code or a fixed statement requires.
Pinned objects (C# fixed statements, GCHandle of type Pinned, buffers used by asynchronous I/O) block compaction. The GC must plan around them, which leaves gaps between them (fragmentation) and can force gen 0 or gen 1 to grow or to be promoted.
Many short-lived pins, as in networking code, are cheap. A few long-lived pins in gen 0 are the real problem. Allocate long-lived pinned buffers once, in the pinned object heap (GC.AllocateArray with pinned set to true) or on the LOH, and reuse them.
Workstation vs server GC
Two GC modes: workstation, where the collection runs on the allocating thread, and server, with one heap and one dedicated GC thread per core.
Workstation GC suits client applications and machines shared by many processes; it can be concurrent. Server GC (setting System.GC.Server) creates one heap and one high-priority GC thread per logical processor by default and collects the heaps in parallel: higher throughput and larger heaps, but more memory and CPU use and pauses that involve all cores. Some hosts choose their own default, and recent versions can adapt the number of heaps to the allocation behavior (DATAS).
In containers, the number of heaps follows the CPU limit unless System.GC.HeapCount sets it. A server GC in a process limited to a few CPUs with a small memory limit can waste memory; check the heap count in the trace.
Official documentation: Workstation and server garbage collection (Microsoft Learn), Garbage collector configuration settings (Microsoft Learn)