How Do Generational Garbage Collectors Use Infant Mortality?
Generational garbage collectors improve memory management performance by dividing the heap into distinct age-based regions and collecting younger objects much more frequently than older ones. This design directly exploits the infant mortality hypothesis, an empirical observation in classical computing that most newly allocated objects become unreachable shortly after creation. By concentrating collection effort on young objects, generational collectors reduce pause times, minimize unnecessary memory scanning, and achieve higher overall throughput.
The Infant Mortality Hypothesis
In most imperative, object-oriented, and functional programs, object lifetimes follow a heavily skewed distribution. Temporary variables, loop iterators, intermediate string buffers, and short-lived closures are allocated rapidly and discarded almost immediately. In contrast, objects that survive several collection cycles—such as application-wide singletons, configuration structures, or long-lived caches—tend to remain reachable for a long time.
If we model object survival probability \(S(t)\) as a function of age \(t\), the hazard rate \(h(t)\) (the conditional probability of dying at age \(t\) given survival up to \(t\)) is highest near \(t = 0\) and decreases rapidly as \(t\) grows. A traditional stop-the-world collector that scans the entire heap of size \(H\) wastes significant work repeatedly tracing long-lived objects whose reachability rarely changes.
Partitioning the Heap into Generations
To take advantage of this lifetime skew, a generational garbage collector partitions the heap into two or more regions:
- Young Generation (Nursery): Where almost all new objects are allocated. It is kept relatively small so that collections are fast and cache-friendly.
- Old Generation (Tenured Space): Where objects that survive a threshold number of young collections are promoted, or tenured. This region is much larger and collected far less frequently.
Some runtimes further subdivide the young generation into an Eden space and two Survivor spaces (often called From and To spaces) to implement copying collection efficiently.
Minor Collections and Copying Efficiency
When the young generation fills up, the collector triggers a minor collection. Because the infant mortality hypothesis holds, the vast majority of objects in the young generation are already dead.
Generational collectors typically use a copying (or semispace) algorithm for the young generation. Instead of sweeping dead objects, the collector traces only the live objects from the roots and copies them to a Survivor space (or promotes them to the old generation). The entire Eden space can then be reclaimed in \(O(1)\) time by simply resetting the allocation pointer.
The cost of a minor collection is proportional to the volume of live objects \(L_{\text{young}}\) rather than the total size of the young generation \(H_{\text{young}}\):
\[\text{Work}_{\text{minor}} \propto L_{\text{young}}\]
Because \(L_{\text{young}} \ll H_{\text{young}}\) under infant mortality, minor collections are extremely fast and require minimal pause times.
Handling Cross-Generational References
Collecting the young generation in isolation introduces a challenge: an object in the old generation might hold a reference to an object in the young generation. Without tracking these pointers, a minor collection could mistakenly reclaim a live young object.
Scanning the entire old generation on every minor collection would defeat the purpose of generational collection. Instead, runtimes use write barriers and remembered sets (often implemented as card tables):
- Write Barriers: Every time the program mutates a reference field in an object, a small snippet of code (the write barrier) checks whether an old-generation object now points to a young-generation object.
- Card Tables: The heap is divided into fixed-size blocks called cards (e.g., 512 bytes). When a write barrier detects a cross-generational store, it marks the corresponding card as dirty.
During a minor collection, the collector only needs to scan the dirty cards in the old generation alongside the standard program roots, keeping minor collection roots small.
Major Collections and Tenuring
Objects that survive a configured number of minor collections—known as the tenuring threshold—are promoted to the old generation. Over time, the old generation fills up with long-lived objects that eventually die, triggering a major (or full) collection.
Major collections are much rarer than minor collections and typically use algorithms suited for higher survival rates, such as mark-sweep-compact or concurrent mark-sweep, to avoid the space overhead of copying a large tenured heap.
Summary of Performance Benefits
By aligning memory management with the empirical distribution of object lifetimes, generational garbage collectors achieve several key advantages:
- Reduced Pause Times: Minor collections scan only a small nursery and a tiny fraction of live objects.
- Fast Allocation: Bump-pointer allocation in the nursery is nearly as fast as stack allocation.
- Better Cache Locality: Newly allocated objects are placed contiguously in memory, and surviving objects are compacted together during copying.
Through this combination of age-based partitioning, copying collection, and write barriers, generational garbage collectors turn the short lifespan of most objects from a burden into a major performance optimization.