π 1. Memory Model in CLR
When your .NET app runs, the CLR manages memory in a space called the Managed Heap.
This heap is divided into three main generations plus the Large Object Heap (LOH):
Generation | Purpose | Typical Object Lifetime |
---|
Gen 0 | For new, short-lived objects | Temporary/local variables |
Gen 1 | Acts as a buffer | Medium-lived objects |
Gen 2 | For long-lived objects | Global/singleton objects |
LOH | For large objects (>85 KB) | Usually Gen 2 behavior |
βοΈ 2. The Core Algorithm β Generational GC Concept
The core principle is:
"Most objects die young."
So .NET uses a generational approach to optimize performance:
This reduces pause times and CPU cost.
π¬ 3. The Step-by-Step Algorithm
Letβs go behind the scenes of what actually happens in CLR.
π§© Step 1: Object Allocation
Every time you create an object (new
keyword), it is allocated sequentially in the Gen 0 segment.
Allocation is extremely fast β just pointer bumping.
var user = new User(); // Allocated in Gen 0
If Gen 0 segment is full β GC starts.
π§Ή Step 2: GC Triggered for Gen 0
When Gen 0 is full, the GC runs:
Stop the world: Application threads pause.
Mark phase: GC marks all objects still reachable (rooted via stack, static variables, CPU registers).
Sweep phase: Unreachable objects are removed.
Compact phase: Survivors are moved down in memory to remove fragmentation.
Objects that survive are promoted to the next generation (Gen 1).
π¦ Step 3: Object Promotion
After a GC run:
Current Generation | Next Step |
---|
Survived Gen 0 | Move to Gen 1 |
Survived Gen 1 | Move to Gen 2 |
Survived Gen 2 | Stay in Gen 2 (until full GC) |
π Promotion is the βaging processβ of an object.
Each time an object survives a collection, its generation count increases by 1 β up to a max of Gen 2.
βοΈ Step 4: Full GC (Gen 2 Collection)
A full GC is triggered when:
Then:
GC scans all generations (0, 1, 2).
Frees memory for unreachable objects.
May compact the heap (in Compacting GC mode).
This is expensive β can cause noticeable application pause.
𧬠5. Internal Algorithm (Simplified Pseudocode)
Hereβs a simplified model of how the CLR GC decides what to collect:
while (applicationRunning)
{
allocateObject();
if (Gen0.isFull)
CollectGeneration(0);
if (Gen1.isFull)
CollectGeneration(1);
if (Gen2.isFull || systemMemoryLow)
CollectGeneration(2);
}
void CollectGeneration(int gen)
{
MarkLiveObjects(gen);
SweepDeadObjects(gen);
CompactHeap(gen);
PromoteSurvivors(gen);
}
Promotion Logic
void PromoteSurvivors(int gen)
{
foreach (var obj in survivors)
{
obj.Generation = Math.Min(obj.Generation + 1, 2);
}
}
π§© 6. Example: Object Lifetime in Action
void Example()
{
// Gen 0 allocation
var temp = new StringBuilder("short-lived");
// Another allocation
var user = new UserProfile(); // long-lived
}
Event | What Happens |
---|
Objects created | Both go into Gen 0 |
Temp no longer used | Collected in next Gen 0 GC |
UserProfile still used | Promoted to Gen 1 |
Survives more GCs | Promoted to Gen 2 |
After a while:
π§ 7. GC Modes and Optimizations (Latest .NET)
In .NET 8 / .NET 9, the GC is smarter:
GC Mode | Description |
---|
Workstation GC | Optimized for low-latency desktop apps |
Server GC | Multi-threaded, high-throughput mode |
Background GC | Runs concurrently without blocking threads |
SustainedLowLatency GC | Used for gaming, trading, or real-time apps (prevents full GC) |
You can control this via AppContext
or runtimeconfig.json
.
β‘ 8. Compacting Algorithm (Memory Defragmentation)
When GC compacts:
Surviving objects are moved to fill holes left by collected ones.
References are updated automatically.
This improves memory locality and cache efficiency.
π This is why you never use raw pointers to managed objects β GC can move them anytime.
π§© 9. Large Object Heap (LOH) Behavior
Objects > 85 KB go into the LOH.
LOH is part of Gen 2.
Until .NET 4.5, it was not compacted.
From .NET 8, LOH compaction is optional (via GCSettings.LargeObjectHeapCompactionMode
).
π 10. Visual Flow Summary
[Object created] β Gen 0
β (survives GC)
β Gen 1
β (survives more GCs)
β Gen 2
β
β (collected in full GC)
π§Ύ 11. Performance Tip Summary
Tip | Description |
---|
Minimize object creation | Keeps Gen 0 small and fast |
Reuse objects / pooling | Avoid constant promotion |
Use using and Dispose() | Helps release unmanaged resources |
Avoid GC.Collect() | Let GC manage timing |
Use profiling tools | (dotMemory, PerfView, Visual Studio Diagnostics) |
π¬ 12. Interview Q&A
βQ1. What is the logic behind object promotion in .NET GC?
Answer:
When an object survives a collection in its current generation, itβs promoted to the next generation (Gen 0 β Gen 1 β Gen 2).
Objects that survive multiple collections are assumed to be long-lived.
βQ2. Why does .NET use generations?
Answer:
Because most objects die young. By collecting only young generations frequently, GC reduces pause times and CPU usage.
βQ3. When does a full GC occur?
Answer:
When Gen 2 or LOH is full, or when system memory pressure is high, or when the developer calls GC.Collect()
manually.
βQ4. What happens during compaction?
Answer:
GC moves surviving objects together to remove fragmentation and updates all references automatically.
βQ5. Can the GC move objects in memory?
Answer:
Yes, except for objects in the pinned state (like fixed buffers or stackalloc
memory).
This movement is why references are managed.
βQ6. Whatβs new in .NET 8 GC?
Answer:
βQ7. What triggers Gen 0 vs Gen 2 collection?
Trigger | Generation |
---|
Gen 0 full | Minor GC |
Gen 1 full | Medium GC |
Gen 2 full or memory low | Full GC |
π Final Thoughts
Gen 0: For short-lived objects β collected frequently
Gen 1: For medium-lived β collected occasionally
Gen 2: For long-lived β collected rarely
The CLR GC algorithm is a mark-sweep-compact generational collector β highly optimized for real-world workloads.
In .NET 8+, itβs so smart that manual memory management is almost never required β just follow clean object lifetime practices, and GC will handle the rest.