Measuring the heap size at a fixed interval (e.g., every second) can be completely misleading because a lot can happen between measurements.For example, consider measuring the heap size once per second. In this scenario, you can see the GC happen because the heap size drops.

Table 1: A Visible GC

SecondActionHeap Size at End of Second
1allocated 1 GB1 GB
2allocated 2 GB3 GB
3allocated 0 GB3 GB
4GC happens, then allocated 1 GB1.5 GB
5allocated 3 GB4.5 GB

However, in the next scenario, a GC happens but is immediately followed by more allocations. From the measurements alone, it’s impossible to tell that a collection even occurred.

Table 2: A Hidden GC

SecondActionHeap Size at End of Second
1allocated 1 GB1 GB
2allocated 2 GB3 GB
3GC happens, then allocated 2 GB3 GB
4allocated 1 GB4 GB

Warning

Point-in-time heap snapshots can hide collections.
The only reliable measurement is before a GC starts and after it finishes.


The Allocation Budget: Your Spending Limit 💳

The allocation budget is the amount of memory a program is allowed to allocate before a GC is triggered.

Tip

Think of it as a credit card limit. Once you’ve spent the full amount, you have to stop and deal with the bill (the GC).

This budget is conceptually the difference between the heap size after the last GC and the heap size right before the next one.
While technical details like object pinning can complicate the exact number, the core idea remains: it’s the amount of allocation “allowed” between cleanups.


Generational GC: A House with Three Rooms 🏠

The .NET GC is generational, dividing the heap into three areas based on the age of objects:

  • Gen 0 (The Entryway): All new, small objects start here. This room is for short-lived items and gets cleaned frequently with quick ephemeral GCs.
  • Gen 1 (The Hallway): Objects that survive a Gen 0 cleanup are moved here. It acts as a buffer.
  • Gen 2 (The Storage Room): Objects that survive a Gen 1 cleanup are promoted here. It holds long-lived objects, and cleaning it is an expensive full GC, so it happens much less often.

Important

An object can only be collected when its generation is being cleaned.
A Gen 2 object will survive indefinitely, no matter how many Gen 0 or Gen 1 GCs run.


Visualization 🖼️

Here’s a simple diagram showing the object life cycle through generations:

flowchart LR
    A[New Object in Gen 0 - Entryway] -->|Survives GC| B[Gen 1 - Hallway]
    B -->|Survives GC| C[Gen 2 - Storage Room]
    A -->|Collected| X((Freed))
    B -->|Collected| X
    C -->|Collected in Full GC| X