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.
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
| Stack | Heap | |
|---|---|---|
new (class instances, arrays, strings, boxed values) | ||
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 garbageHow the garbage collector works
Mark: starting from the roots (local variables on every thread’s stack, static fields, CPU registers), follow every reference and mark each object reached. Sweep / compact: everything not marked is garbage. Its memory is reclaimed, and surviving objects may be moved together to avoid gaps. Promote: survivors move to an older generation.
Generations
Why it matters for performance
// ✗ 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 StringBuilderinstead of+=in loops.Reuse collections in hot paths ( Clear()instead ofnew). 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 objectorArrayList.Use ArrayPool<T>.SharedandSpan<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
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
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 newlive 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.