<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. C#
  3. Advanced C#
All lessons in this course

Lesson 45 of 60 16 minAdvanced

Memory: stack, heap and garbage collection

Where your variables and objects really live, how the garbage collector frees memory, and how to write code that creates less garbage.

You never call delete in C#, yet memory is freed. This lesson explains how, because understanding memory is the key to fast, smooth software, especially in 3D and BIM tools that handle millions of objects.

Stack and heap

StackHeap
Holdsmethod frames: parameters and local variables (value types, and references)objects created with new (class instances, arrays, strings, boxed values)
Sizesmall (about 1 MB per thread)large (as much as the machine allows)
Freedautomatically when the method returnsby the garbage collector, some time after nothing references the object
Speedextremely fastallocation is fast; collection costs time
WhereItLives.cs
void Calculate(){    int count = 3;                       // stack: the value 3    double[] loads = new double[count];  // stack: reference → heap: the array    var beam = new Beam("B1");           // stack: reference → heap: the Beam object    Point3 p = new Point3(1, 2, 3);      // struct local: stack, no heap allocation}   // the frame is gone; the array and Beam are now unreachable garbage

How the garbage collector works

  1. Mark: starting from the roots (local variables on every thread’s stack, static fields, CPU registers), follow every reference and mark each object reached.
  2. Sweep / compact: everything not marked is garbage. Its memory is reclaimed, and surviving objects may be moved together to avoid gaps.
  3. Promote: survivors move to an older generation.
Interactive

Garbage collection, step by step

reachable from a root (a variable or field still using it) · unreachable (garbage). New objects start in generation 0.

Gen 0new, collected often
empty
collections: 0
Gen 1survived once
Viewport
collections: 0
Gen 2long-lived, collected rarely
ModelSettings
collections: 0
Add some temporary objects (like strings built in a loop) and run GC.Collect(0).

Generations

Most objects die young: temporary strings, LINQ iterators, short-lived lists. So .NET collects generation 0 (new objects) very often and cheaply, generation 1 less often, and generation 2 (long-lived objects like your loaded model) rarely, because a gen-2 collection must examine much more memory. Objects larger than about 85 KB go straight to the Large Object Heap, collected with gen 2.

Why it matters for performance

While the GC works, your threads may pause. In a 3D viewport rendering 60 frames per second, each frame has about 16 ms; allocating lots of garbage every frame triggers frequent collections and visible stutter. Fewer allocations → fewer collections → smoother apps.

LessGarbage.cs
// ✗ allocates a new list and an iterator every framevoid OnFrame(){    var visible = members.Where(m => m.IsVisible).ToList();    Draw(visible);} // ✓ reuse one list; clear it instead of re-creating itprivate readonly List<Member> _visible = new(capacity: 10_000);void OnFrameFast(){    _visible.Clear();    foreach (var m in members)        if (m.IsVisible) _visible.Add(m);    Draw(_visible);}

Habits that reduce allocations

  • Use StringBuilder instead of += in loops.
  • Reuse collections in hot paths (Clear() instead of new). Give lists an initial capacity when you know the size.
  • Use small structs for points and vectors stored in large arrays.
  • Avoid boxing: use generic collections, not object or ArrayList.
  • Use ArrayPool<T>.Shared and Span<T> (covered later) for temporary buffers.
  • Measure first: Visual Studio’s Diagnostic Tools and the .NET profilers show allocations and GC pauses.

Memory leaks in a managed language

The GC frees only unreachable objects. If something still references an object you no longer need, it is never freed. Common causes: forgotten event subscriptions, static lists that only grow, caches without a size limit, and closures capturing big objects.

Quick check

When can the garbage collector free an object?

Quick check

Why are generation-0 collections cheap?

Practice

This method is called 60 times per second and causes GC stutter. Rewrite it to allocate nothing per call (hint: reuse a field). double[] GetDepths(Member[] ms) { var d = new double[ms.Length]; for (int i = 0; i < ms.Length; i++) d[i] = ms[i].Depth; return d; }

Show the solution
Solution.cs
private double[] _depths = Array.Empty<double>(); ReadOnlySpan<double> GetDepths(Member[] ms){    if (_depths.Length < ms.Length)        _depths = new double[ms.Length];        // grows rarely, then reused     for (int i = 0; i < ms.Length; i++)        _depths[i] = ms[i].Depth;     return _depths.AsSpan(0, ms.Length);       // a view, no copy}

Remember

  • Method frames live on the stack; objects from new live on the heap.
  • The GC marks objects reachable from roots and frees the rest.
  • Generations make collections cheap: most objects die young.
  • Fewer allocations in hot paths means fewer GC pauses and smoother apps.
0 of 60 lessons completed