Yes, we can absolutely do this in one self-contained program ๐
Youโll be able to:
- See GC before/after each scenario
- Trigger Gen0 / Gen1 / Gen2 collections
- Observe heap size and collection counts
- Use this with dotnet-counters / dotMemory / VS profiler if you want
Below is a single Program.cs you can drop into a new console app.
1๏ธโฃ Create the project
dotnet new console -n GcGenerationsDemo
cd GcGenerationsDemo
Code language: JavaScript (javascript)
Replace all contents of Program.cs with this:
2๏ธโฃ How to run and โexperienceโ GC
From inside the project folder:
dotnet run
The program will walk you through:
- Startup stats
- Gen0 scenario โ short-lived allocations
- Gen1 scenario โ medium-lived allocations
- Gen2 scenario โ long-lived allocations
Each step prints:
Gen0/Gen1/Gen2 collection countsTotal managed heap (MB)
Youโll see:
- Gen0 counts jump a lot in the first scenario.
- Gen1 counts increase in the second scenario.
- Gen2 counts and heap size behavior change in the third scenario.
3๏ธโฃ Optional: Watch it live with dotnet-counters
In another terminal, while the app is running:
- Get process list:
dotnet-counters ps - Find your
GcGenerationsDemoPID, then:dotnet-counters monitor --process-id <PID> System.Runtime
Watch:
gc-heap-sizegen-0-gc-countgen-1-gc-countgen-2-gc-count
Run each scenario and youโll see the counters move in sync with console output.
4๏ธโฃ How this maps to Gen0 / Gen1 / Gen2 concepts
- Gen0 scenario
- Many tiny, short-lived objects
- Mostly collected in Gen0
- Youโll see Gen0 collections spike
- Gen1 scenario
- Objects kept alive briefly in a
List<byte[]> - They survive at least one collection โ promoted to Gen1
- We force Gen1 collections and then free references
- You see Gen1 counts increase, heap shrink
- Objects kept alive briefly in a
- Gen2 scenario
- Objects stored in a static list (
LongLivedHolder.Buffers) - They are long-lived; promoted to Gen2
- Even after Gen2 collection, many remain because references are still held
- This is how leaks and long-lived caches behave
- Objects stored in a static list (

1๏ธโฃ How to read the four numbers
Each PrintGcStats gives you:
- Gen0 collections โ how many times .NET cleaned short-lived objects
- Gen1 collections โ how many times it cleaned objects that survived Gen0
- Gen2 collections โ how many times it cleaned long-lived objects
- Total managed heap โ how much managed memory is still in use after GC (roughly)
These counters are cumulative since process start.
2๏ธโฃ Gen0 scenario โ short-lived objects
You saw:
=== Gen0 Scenario: Short-lived objects ===
--- GC Stats: Before Gen0 work ---
Gen0 collections: 0
Gen1 collections: 0
Gen2 collections: 0
Total managed heap: 0 MB
Gen0 scenario completed in 35 ms
--- GC Stats: After Gen0 forced collection ---
Gen0 collections: 71
Gen1 collections: 1
Gen2 collections: 0
Total managed heap: 0 MB
What happened?
- We allocated millions of tiny objects in a loop.
- They were not stored anywhere, so they died quickly.
- GC cleaned them mostly in Gen0:
- Gen0 collections:
0 โ 71โ
- Gen0 collections:
- A few objects survived briefly โ promoted to Gen1 once:
- Gen1 collections:
0 โ 1
- Gen1 collections:
- After the forced collection:
Total managed heap: 0 MB(rounded) โ means almost nothing left.
๐ Interpretation:
โLots of short-lived garbage โ GC handled it cheaply in Gen0.
We generated a ton of allocations, but the memory was fully reclaimed. No leak, GC working as designed.โ
This is typical of request-scoped allocations in APIs when theyโre well-behaved.
3๏ธโฃ Gen1 scenario โ medium-lived objects
You saw:
=== Gen1 Scenario: Medium-lived objects ===
--- GC Stats: Before Gen1 work ---
Gen0 collections: 71
Gen1 collections: 1
Gen2 collections: 0
Total managed heap: 0 MB
Gen1 scenario completed in 2613 ms
--- GC Stats: After Gen1 forced collections ---
Gen0 collections: 627
Gen1 collections: 296
Gen2 collections: 33
Total managed heap: 3131 MB
What did the code do here?
- It allocated a lot of 16 KB buffers and stored them in a
List<byte[]> survivors. - That local list stayed alive for a while โ the buffers survived multiple Gen0 collections.
- That caused:
- Gen0 collections:
71 โ 627(lots of allocations) - Gen1 collections:
1 โ 296(many promotions & cleanups) - Gen2 collections:
0 โ 33(some long-lived promotions too)
- Gen0 collections:
Why is heap so big here (3131 MB)?
- Right after the scenario, before the runtime has fully compacted and reused memory,
GetTotalMemorysees ~3 GB still reserved/used. - These objects were just cleared at the end of the scenario (we call
survivors.Clear()and GC.Collect), but this snapshot is still showing that a lot of memory was in play.
Then before the next scenario you saw:
--- GC Stats: Before Gen2 work ---
...
Total managed heap: 2 MB
So eventually the runtime fully reclaimed it, and the heap dropped back down.
๐ Interpretation:
โHere we created objects that lived longer than Gen0 (in a list).
We see big jumps in Gen1 and Gen2 collections and temporary heap growth (~3 GB).
After clearing references and more GC, memory drops back to a few MB โ no leak, just heavy temporary pressure.โ
This demonstrates:
- Promotion from Gen0 โ Gen1 โ Gen2
- Longer-lived objects = more expensive GC
- Why you donโt want to keep large collections alive longer than necessary.
4๏ธโฃ Gen2 scenario โ long-lived / leaked objects
You saw:
=== Gen2 Scenario: Long-lived objects ===
--- GC Stats: Before Gen2 work ---
Gen0 collections: 627
Gen1 collections: 296
Gen2 collections: 33
Total managed heap: 2 MB
Gen2 scenario completed in 184 ms
--- GC Stats: After Gen2 forced collection ---
Gen0 collections: 693
Gen1 collections: 329
Gen2 collections: 34
Total managed heap: 392 MB
Long-lived objects stored: 50000
Note: Because we still keep references, these objects won't be freed.
What did the code do here?
- Allocated 50,000 ร 8 KB buffers โ ~400 MB.
- Stored them in
LongLivedHolder.Buffers, which isstatic. - We never clear that list โ those objects are effectively long-lived.
Even after a full Gen2 collection:
- Gen2 collections:
33 โ 34(we forced a full GC) - But heap only drops to:
Total managed heap: 392 MB
- And we still have:
Long-lived objects stored: 50000
๐ Interpretation:
โThese are long-lived objects (or a leak).
Even after a full Gen2 GC, almost 400 MB remains because weโre still holding references in a static list.
This is what a memory leak / long-lived cache looks like in production:
Gen2 collections happen, but memory never really goes down.โ
This is the pattern youโd see in:
- Static caches that donโt evict
- Static lists/dicts that only grow
- Singletons holding on to big data
- Event handler leaks, etc.
5๏ธโฃ How to explain (short version)
โIn the Gen0 scenario, we created millions of tiny, short-lived objects.
GC handled them mostly in Gen0 (71 collections), and after GC, memory is basically 0 MB.
This is healthy, short-lived garbage.
In the Gen1 scenario, we kept objects alive for a while in a list.
They survived Gen0, got promoted to Gen1 and some to Gen2.
We see big Gen1/Gen2 collection counts and temporary heap growth to ~3 GB, but after clearing references, the heap returns to a small size.
This shows the cost of medium-lived objects.
In the Gen2 scenario, we stored 50k buffers in a static list.
Even after a full Gen2 collection, we still use ~392 MB.
Because the app still references these objects, the GC cannot free them.
This is exactly how long-lived objects and memory leaks behave in real .NET apps.โ
Iโm a DevOps/SRE/DevSecOps/Cloud Expert passionate about sharing knowledge and experiences. I have worked at Cotocus. I share tech blog at DevOps School, travel stories at Holiday Landmark, stock market tips at Stocks Mantra, health and fitness guidance at My Medic Plus, product reviews at TrueReviewNow , and SEO strategies at Wizbrand.
Do you want to learn Quantum Computing?
Please find my social handles as below;
Rajesh Kumar Personal Website
Rajesh Kumar at YOUTUBE
Rajesh Kumar at INSTAGRAM
Rajesh Kumar at X
Rajesh Kumar at FACEBOOK
Rajesh Kumar at LINKEDIN
Rajesh Kumar at WIZBRAND
Find Trusted Cardiac Hospitals
Compare heart hospitals by city and services โ all in one place.
Explore Hospitals
This article does an excellent job of breaking down how memory management works in .NET through its generational garbageโcollection strategy. By explaining that new objects start in Genโฏ0 โ where most shortโlived allocations get collected quickly โ and that only objects which โsurviveโ get promoted to Genโฏ1 and eventually Genโฏ2, it makes clear how GC optimizes for both performance and memory usage. The discussion of how the garbage collector determines which objects are reachable (roots, references, etc.) and safely reclaims unreachable objects helps demystify what often feels like a โblack box.โ For developers building longโrunning applications, this post offers valuable insight into why allocation patterns matter, how GC impacts memory lifetimes, and how to design code to minimize unwanted memory retention.
this article explains very clearly how garbage collection works under the hood in .NET (with .NET / CLR), especially the logic behind generational GC via Garbage Collector (GC) and its โGen0 โ Gen1 โ Gen2โ model. It shows why most objects โ like temporary locals or shortโlived data โ go into Gen0, get collected frequently, and rarely promoted, while only longerโlived survivors move to Gen1, and ultimately Gen2, which keeps longโlived or โstatic/ cachedโ objects. This generational strategy optimizes for performance by avoiding repeated scanning of the entire heap, reclaiming shortโlived objects cheaply, and only occasionally collecting longโlived data. The articleโs example program (with forced collections and heapโsize / collectionโcount outputs) is especially useful: it makes abstract GC concepts tangible โ you can actually see Genโ0 spikes with many shortโlived allocations, observe promotions to Gen1/Gen2 with mediumโlived data, and understand how memoryโleaks or longโlived caches behave when references persist. For anyone designing memoryโsensitive .NET applications, this tutorial is an excellent resource to build intuition about when GC runs, why allocation patterns matter, and how to avoid unintended memory retention.