How does garbage collection actually work?
By determining which objects are still reachable from your running program and reclaiming everything else. The key insight is that it does not detect garbage — it detects live data, and treats the rest as garbage by elimination.
The reachability model. Collection starts from a set of roots: local variables on the stack, CPU registers, static and global fields. Anything reachable by following references from a root is live. Anything not reachable cannot be used again by any code path, so it can be freed — which is why a cycle of objects referring only to each other is still collectable, unlike under naive reference counting.
The main algorithms:
Mark and sweep. Traverse from the roots marking live objects, then sweep the heap freeing the unmarked. Simple, and leaves fragmentation.
Copying collectors, which move live objects into a fresh region and abandon the old one wholesale. Allocation afterwards is just a pointer bump, which is extremely fast, at the cost of double the memory.
Generational collection, the most consequential idea, built on the observation that most objects die young. The heap is split by age; the young generation is collected frequently and cheaply, and survivors are promoted. This is why allocating many short-lived objects is far cheaper in a managed language than intuition suggests.
Reference counting, used by some runtimes, which frees immediately when a count drops to zero but cannot handle cycles without an additional collector.
Why pauses happen. A collector that moves objects must ensure the program does not observe a half-moved heap — the "stop the world" pause. Modern concurrent and incremental collectors do most work alongside the running program, using write barriers to track changes, reducing pauses from hundreds of milliseconds to single digits. Latency and throughput trade off, which is why runtimes ship several collectors to choose between.
What it does not solve. Memory leaks remain possible whenever something stays reachable but useless — a growing cache, an unremoved event listener, a static collection. The collector is doing its job correctly; the reference is the bug.