The Garbage Collector
You write `new Order()` ten thousand times in a loop and never free one of them, yet memory stays flat. Something is walking your objects, deciding which are still in use, and reclaiming the rest behind your back.
- ▸The collector never hunts for dead objects; it finds the live ones and frees everything else without inspecting it.
- ▸Surviving a collection is what ages an object into the costlier tier, so the long-lived object is the expensive one.
- ▸The same object can sit at a different address after each collection, and a 85,000-byte array follows entirely separate rules.
A static field still holds an object you stopped using an hour ago:
static List<User> _online = new(); // ...an hour later, nothing in your code reads _online again
Nothing will ever touch that list again, yet the Users inside it are never freed. To see why, watch what the collector actually does, because it is the opposite of what most people picture.
It does not scan the heap hunting for dead objects. It starts from a fixed set of roots: the static fields, the locals on each thread's stack, the values in CPU registers. From every root it follows each reference, and every reference out of every object it lands on, building the set of objects reachable from a root. That walk is the whole job. Anything the walk never lands on is, by definition, garbage, and its memory is reclaimed without the collector ever inspecting it.
So "alive" has a precise meaning, and it is not "still useful to you." An object is alive exactly when the collector can reach it from a root. That _online list is rooted by a static field, so every User it holds is reachable, so they all live, forever, no matter that your code is done with them. Reachability, not your intent, decides life. A leak here is simply an object you forgot you were still rooting.