Created
November 26, 2025 07:15
-
-
Save devops-school/99d99d45e2aa02fc8b64427fe4ad4182 to your computer and use it in GitHub Desktop.
DOTNET: Understanding Garbage Collection with Gen0, Gen1, Gen2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| class Program | |
| { | |
| static void Main(string[] args) | |
| { | |
| Console.WriteLine("========================================="); | |
| Console.WriteLine(" .NET GC Generations Demo (Gen0/1/2) "); | |
| Console.WriteLine("========================================="); | |
| Console.WriteLine($"Is Server GC: {System.Runtime.GCSettings.IsServerGC}"); | |
| Console.WriteLine(); | |
| PrintGcStats("Startup"); | |
| Pause("Press ENTER to run Gen0 (short-lived objects) scenario..."); | |
| RunGen0Scenario(); | |
| Pause("Press ENTER to run Gen1 (mid-lived objects) scenario..."); | |
| RunGen1Scenario(); | |
| Pause("Press ENTER to run Gen2 (long-lived objects) scenario..."); | |
| RunGen2Scenario(); | |
| Pause("Done. Press ENTER to exit."); | |
| } | |
| // ----------------------------- | |
| // Scenario 1: Gen0 focus | |
| // Lots of short-lived objects that die quickly | |
| // ----------------------------- | |
| static void RunGen0Scenario() | |
| { | |
| Console.WriteLine(); | |
| Console.WriteLine("=== Gen0 Scenario: Short-lived objects ==="); | |
| PrintGcStats("Before Gen0 work"); | |
| var sw = Stopwatch.StartNew(); | |
| // Allocate many small, short-lived objects | |
| for (int i = 0; i < 5_000_000; i++) | |
| { | |
| // Each allocation is ~64 bytes | |
| var bytes = new byte[64]; | |
| // We don't store 'bytes' anywhere -> eligible for Gen0 collection | |
| bytes[0] = 1; | |
| } | |
| // Force a Gen0 collection only (ephemeral) | |
| GC.Collect(0, GCCollectionMode.Forced, blocking: true, compacting: false); | |
| sw.Stop(); | |
| Console.WriteLine($"Gen0 scenario completed in {sw.ElapsedMilliseconds} ms"); | |
| PrintGcStats("After Gen0 forced collection"); | |
| } | |
| // ----------------------------- | |
| // Scenario 2: Gen1 focus | |
| // Objects survive a bit (promoted), then are freed | |
| // ----------------------------- | |
| static void RunGen1Scenario() | |
| { | |
| Console.WriteLine(); | |
| Console.WriteLine("=== Gen1 Scenario: Medium-lived objects ==="); | |
| PrintGcStats("Before Gen1 work"); | |
| var sw = Stopwatch.StartNew(); | |
| // These objects will be kept alive through at least one Gen0 GC | |
| var survivors = new List<byte[]>(); | |
| for (int i = 0; i < 200_000; i++) | |
| { | |
| // Each ~16 KB, total ~3.2 GB allocated over time (but reused by GC) | |
| var buffer = new byte[16 * 1024]; | |
| buffer[0] = 1; | |
| buffer[buffer.Length - 1] = 2; | |
| survivors.Add(buffer); | |
| } | |
| // Force a Gen1 collection (which also collects Gen0) | |
| GC.Collect(1, GCCollectionMode.Forced, blocking: true, compacting: false); | |
| // Now clear references so they become collectible | |
| survivors.Clear(); | |
| // Another collection so they actually disappear | |
| GC.Collect(1, GCCollectionMode.Forced, blocking: true, compacting: false); | |
| sw.Stop(); | |
| Console.WriteLine($"Gen1 scenario completed in {sw.ElapsedMilliseconds} ms"); | |
| PrintGcStats("After Gen1 forced collections"); | |
| } | |
| // ----------------------------- | |
| // Scenario 3: Gen2 focus | |
| // Long-lived objects (static / global) promoted and retained | |
| // ----------------------------- | |
| static void RunGen2Scenario() | |
| { | |
| Console.WriteLine(); | |
| Console.WriteLine("=== Gen2 Scenario: Long-lived objects ==="); | |
| PrintGcStats("Before Gen2 work"); | |
| var sw = Stopwatch.StartNew(); | |
| // These allocations are stored in a static holder and never cleared | |
| for (int i = 0; i < 50_000; i++) | |
| { | |
| // Each ~8 KB -> total ~400 MB if all kept | |
| var buffer = new byte[8 * 1024]; | |
| buffer[0] = 1; | |
| buffer[buffer.Length - 1] = 2; | |
| LongLivedHolder.Buffers.Add(buffer); | |
| } | |
| // Force a full Gen2 collection (collects Gen0, Gen1, Gen2) | |
| GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true); | |
| sw.Stop(); | |
| Console.WriteLine($"Gen2 scenario completed in {sw.ElapsedMilliseconds} ms"); | |
| PrintGcStats("After Gen2 forced collection"); | |
| Console.WriteLine($"Long-lived objects stored: {LongLivedHolder.Buffers.Count}"); | |
| Console.WriteLine("Note: Because we still keep references, these objects won't be freed."); | |
| } | |
| // ----------------------------- | |
| // Helpers | |
| // ----------------------------- | |
| static void PrintGcStats(string label) | |
| { | |
| long gen0 = GC.CollectionCount(0); | |
| long gen1 = GC.CollectionCount(1); | |
| long gen2 = GC.CollectionCount(2); | |
| long totalBytes = GC.GetTotalMemory(forceFullCollection: false); | |
| Console.WriteLine($"--- GC Stats: {label} ---"); | |
| Console.WriteLine($" Gen0 collections: {gen0}"); | |
| Console.WriteLine($" Gen1 collections: {gen1}"); | |
| Console.WriteLine($" Gen2 collections: {gen2}"); | |
| Console.WriteLine($" Total managed heap: {totalBytes / (1024 * 1024)} MB"); | |
| Console.WriteLine(); | |
| } | |
| static void Pause(string message) | |
| { | |
| Console.WriteLine(); | |
| Console.WriteLine(message); | |
| Console.ReadLine(); | |
| } | |
| } | |
| // Static holder to simulate Gen2 / long-lived objects | |
| static class LongLivedHolder | |
| { | |
| public static readonly List<byte[]> Buffers = new(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment