Most objects die young -- that's the generational hypothesis, and it's why the JVM splits its heap into a small young generation (collected often, cheaply) and an old generation (collected rarely, expensively). A minor collection is a COPYING collector: it doesn't scan the whole young generation looking for garbage to reclaim -- instead, it traces the LIVE objects from a set of roots and copies only those into a fresh space. Everything else -- every dead object -- is simply left behind, untouched. When the trace finishes, the entire old semispace (dead objects and all) is abandoned at once. This is exactly why a minor GC's cost is proportional to how much data SURVIVED, not to how large the heap is.
Implement minor_gc(heap, roots, promotion_threshold):
- Trace every object reachable from
rootsthroughheap.from_space(following each visited object's"refs"list). Objects not reachable from any root must never be copied intoto_space,old_gen, or inspected beyond that initial reachability trace. - For each live object, its new age is
age + 1. If that new age is>= promotion_threshold, copy it intoheap.old_gen(still at its new age); otherwise copy it intoheap.to_spaceat its new age. - After every live object has been copied,
heap.from_spacebecomes what was just built inheap.to_space, andheap.to_spaceresets to{}-- ready for the next collection.
Return {"visited": int, "promoted": [id, ...], "survived": [id, ...]}.
"visited" is the number of objects the trace actually touched --
this should equal the number of LIVE objects, regardless of how many
dead objects also happened to be sitting in from_space.
Example
heap = Heap()
heap.allocate("A", refs=["B"])
heap.allocate("B", refs=[])
heap.allocate("garbage", refs=[]) # unreachable from any root
stats = minor_gc(heap, roots=["A"], promotion_threshold=100)
stats["visited"] # -> 2 (A and B -- garbage was never touched)
"garbage" in heap.from_space # -> False
heap.from_space["A"]["age"] # -> 1
Constraints
- The object graph may contain cycles -- trace with a visited set.
promotion_thresholdis compared against an object's age AFTER this collection increments it, not its age before.rootsmay reference ids not present inheap.from_space(e.g. already promoted toold_genin an earlier collection) -- skip those rather than erroring.
Real-world grounding
- HotSpot's default young-generation collector (the serial
collector's
DefNewGeneration, whosefrom/tosurvivor-space swap and age-based tenuring this exercise models directly):src/hotspot/share/gc/serial/defNewGeneration.cpp,defNewGeneration.hpp
Real HotSpot also tracks old-generation-to-young-generation pointers
via a card table / remembered set (so roots aren't only the explicit
stack/register set -- an old object holding a reference into the
young generation counts too) -- this exercise takes roots as a
given, simplified input rather than deriving it from a card table.