Lesson 23: Memory Management & Garbage Collector Internals
Understand how .NET manages memory automatically — and how to work with the garbage collector, not against it.
What You'll Learn
- • How the .NET garbage collector works (Mark → Sweep → Compact)
- • Generational GC: Gen 0, Gen 1, Gen 2, and the Large Object Heap
- • Weak references and when to use them
- • ArrayPool and strategies to reduce allocation pressure
🧠 Real-World Analogy
Think of the GC as an automatic office cleaner. Gen 0 is your desk (cleaned daily), Gen 1 is the filing cabinet (cleaned weekly), and Gen 2 is the storage room (cleaned rarely). The LOH is the warehouse — it's expensive to reorganize, so you reuse boxes instead of buying new ones.
How the Garbage Collector Works
The .NET GC uses a generational, mark-and-sweep algorithm. When memory pressure rises, the GC pauses your application, marks all reachable objects, sweeps (frees) unreachable ones, and optionally compacts the heap to eliminate fragmentation.
| Generation | Contains | Collected |
|---|---|---|
| Gen 0 | Newly allocated objects | Very frequently (ms) |
| Gen 1 | Objects that survived Gen 0 | Less often |
| Gen 2 | Long-lived objects | Rarely (expensive) |
| LOH | Objects ≥ 85 KB | With Gen 2 (not compacted by default) |
GC Generations & Collection Counts
Observe how objects move through GC generations and check memory statistics.
using System;
class Program
{
static void Main()
{
// Create objects in different generations
var shortLived = new byte[100]; // Gen 0
var mediumLived = new byte[1000]; // Will promote to Gen 1
// Force promotion by surviving collections
GC.Collect(0); // shortLived moves to Gen 1 if still referenced
GC.Collect(1); // moves to Gen 2 if still referenced
Console.WriteLine($"shortLived generation: {GC.Get
...Weak References
A WeakReference<T> lets you hold a reference to an object without preventing the GC from collecting it. This is perfect for caches where you want to keep data if memory allows, but don't want to cause OutOfMemoryException.
WeakReference<T> Demo
See how the GC collects weakly-referenced objects but keeps strong references alive.
using System;
class ExpensiveResource
{
public string Name { get; set; }
public byte[] Data { get; set; }
public ExpensiveResource(string name, int sizeKB)
{
Name = name;
Data = new byte[sizeKB * 1024];
Console.WriteLine($"Created: {Name} ({sizeKB} KB)");
}
~ExpensiveResource()
{
Console.WriteLine($"Finalized: {Name}");
}
}
class Program
{
static void Main()
{
// Strong reference — GC cannot collect
...Large Object Heap & ArrayPool
Objects 85,000 bytes or larger are allocated on the Large Object Heap (LOH), which is collected with Gen 2 and not compacted by default. This can cause fragmentation. The solution? Use ArrayPool<T> to rent and return large arrays instead of allocating new ones.
LOH & ArrayPool
Compare LOH allocations vs ArrayPool rentals to reduce GC pressure.
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
// Objects >= 85,000 bytes go to the Large Object Heap (LOH)
Console.WriteLine("=== Large Object Heap Demo ===\n");
// Small object → goes to SOH (Small Object Heap)
var small = new byte[1000];
Console.WriteLine($"Small array (1 KB): Gen {GC.GetGeneration(small)}");
// Large object → goes to LOH (treated as Gen 2)
var large = new byte[100
...Pro Tip
Use GC.TryStartNoGCRegion() in latency-critical sections (like game loops) to temporarily prevent GC pauses. Always pair with GC.EndNoGCRegion().
Common Mistakes
- • Calling
GC.Collect()in production — let the GC decide when to collect - • Allocating large arrays in tight loops — use ArrayPool instead
- • Relying on finalizers for cleanup — use IDisposable and
using
Lesson Complete!
You now understand .NET's generational GC, the LOH, weak references, and ArrayPool. Next, explore unsafe code and Span<T> for zero-allocation performance.
Sign up for free to track which lessons you've completed and get learning reminders.