Roadmap › .NET Internals — CLR, CTS, CLS, IL, JIT, Memory Management & Garbage Collection › Module: Memory Management
Lessons in this course (11)
6. The garbage collector in depth — generations, LOH, POH and GC modes
Intermediate★ Must knowLesson 6 of 11
Allocation: the GC hands each thread an allocation context; new just bumps a pointer — allocation is very cheap.
Collection — mark, (sweep), compact:
- Suspend managed threads at safe points (for blocking GCs).
- Mark — starting from roots (stack slots and registers, static fields, GC handles, finalization queue), follow references and mark reachable objects.
- Plan/compact — slide surviving objects together, update all references; free space becomes one contiguous block (no fragmentation). Sometimes it only sweeps (builds a free list) when compaction isn't worth it.
- Resume threads.
Generations (the "weak generational hypothesis": most objects die young):
| Generation | Holds | Collected |
|---|---|---|
| Gen 0 | Newly allocated small objects | Very often, very fast (ms or less) |
| Gen 1 | Survivors of one gen-0 GC — buffer between young and old | Often |
| Gen 2 | Long-lived objects (caches, singletons, static data) | Rarely; a "full GC" — most expensive |
| LOH — Large Object Heap | Objects ≥ 85,000 bytes (big arrays, big strings) | Only with gen 2; not compacted by default (can fragment) |
| POH — Pinned Object Heap (.NET 5+) | Objects allocated as pinned (GC.AllocateArray(..., pinned: true)) | With gen 2, never moved |
Objects that survive a collection are promoted to the next generation. Gen-2 objects referencing gen-0 objects are tracked with a card table so gen-0 GCs don't have to scan all of gen 2.
GC modes:
| Mode | Behaviour | Default for |
|---|---|---|
| Workstation | One heap; collections on the triggering thread; low pause, less throughput | Desktop/console apps |
| Server | One heap + GC thread per core; higher throughput, more memory | ASP.NET Core apps |
| Concurrent / Background GC | Gen-2 marking runs concurrently with the app; gen 0/1 still pause briefly | On by default |
| DATAS (dynamic adaptation, .NET 8 opt-in, on by default with Server GC in .NET 9) | Server GC that grows/shrinks heap count to the load — less memory for small containers | Containers |
xml
<!-- .csproj -->
<PropertyGroup>
<ServerGarbageCollector>true</ServerGarbageCollector>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>csharp
Console.WriteLine(GC.CollectionCount(0) + " gen0, " + GC.CollectionCount(2) + " gen2");
Console.WriteLine(GC.GetTotalMemory(forceFullCollection: false) / 1_048_576 + " MB");
var info = GC.GetGCMemoryInfo(); // heap size, fragmentation, pause durations
// GC.Collect(); // almost never call this in production codeWhat triggers a GC: gen-0 budget exhausted (most common), GC.Collect(), low memory notification from the OS, LOH allocation thresholds, container memory limits.