Describe how a mark-sweep garbage collection algorithm works, phase by phase, and explain the characteristic problem it leaves behind in the heap.
answer
- mark from roots, sweep the rest
- mark cost ~ live data; sweep cost ~ heap size
- non-moving: addresses stable, no pointer fixup
- fragmentation -> free list, not bump pointer
- big allocation fails despite free bytes
basics
~20 sMark: trace from the roots and flag every reachable object. Sweep: walk the heap and return every unflagged object's space to a free list. Nothing moves, so surviving objects stay put and the reclaimed gaps between them leave the heap fragmented.
solid answer
~60 sMark-sweep runs in two phases over the heap. **Mark** starts from the roots - thread stacks, static fields, and similar entry points - and traverses every reference it can reach, setting a mark bit for each object found. Cost is proportional to the amount of *live* data, since dead objects are never visited. **Sweep** walks the heap linearly, and every object without a mark bit has its space added to a free list. Cost is proportional to the *size of the heap*, because the whole address range must be inspected. Mark bits are then cleared for the next cycle. The defining property is that it is **non-moving**: surviving objects keep their addresses. That makes it cheap for anything holding a raw pointer to an object and avoids the cost of updating references. The price is **fragmentation**: free space ends up as many small holes between survivors. Allocation must consult a free list and find a fitting hole rather than simply bumping a pointer, allocation of large contiguous blocks can fail even when total free bytes look ample, and locality of newly allocated objects degrades.
go deeper
Name the two phases, say that liveness is decided by tracing from roots, and state that fragmentation is the leftover problem because nothing moves.
Add the cost model - marking scales with live data, sweeping with heap size - and explain the free-list allocation consequence versus bump-pointer allocation.
Discuss practical mitigations (lazy sweeping, size-class free lists, occasional compaction) and the failure mode where a large contiguous allocation fails despite ample free bytes.
Frame non-moving collection as a deliberate constraint chosen for address stability and interoperability with code holding raw pointers, and weigh it against the allocator and locality costs it imposes system-wide.
## The two phases Mark-sweep is the oldest of the tracing collection strategies and the base that others are described against. **Marking.** The collector begins from a set of *roots* - references that are live by definition, such as local variables on thread stacks, static fields, and references held by native code. It follows every reference reachable from them, transitively, and records each object it reaches as live, typically by setting a bit in a side bitmap or in the object header. Anything not reached at the end of the traversal is unreachable and therefore garbage. Note that the collector never identifies garbage directly: it identifies what is live and infers the rest. Marking cost scales with the volume of live data and with the shape of the object graph, not with the amount of garbage. **Sweeping.** The collector then walks the heap from one end to the other. Because objects are laid out with known sizes, it can step from object to object. Every object without a mark is dead; its space is coalesced with adjacent free space where possible and recorded in a free list. Marks are cleared, either during the sweep or by flipping the sense of the mark bit for the next cycle. Sweep cost scales with the *total heap size*, since dead and live memory alike must be walked. ## The defining property: nothing moves No surviving object changes address. Two consequences follow directly. The advantage: every existing reference to a survivor remains valid without being rewritten. There is no pointer-fixup pass, and code outside the collector's control that holds a direct address - native code through JNI, for instance - is not invalidated. Non-moving collection is also comparatively simple to implement. The disadvantage: reclaimed space appears exactly where dead objects happened to sit, scattered between survivors. This is **fragmentation**. ## Why fragmentation matters in practice 1. **Allocation gets slower.** With a compacted or freshly emptied space, allocating is a pointer bump: take the next N bytes and advance. With a fragmented heap, the allocator must search a free list for a hole big enough, and maintain size-class structures to make that search tolerable. 2. **Large allocations can fail even with plenty of free memory.** If free space totals 200 MB but the largest contiguous hole is 2 MB, an 8 MB array cannot be allocated. The collector may be forced into a more expensive compacting collection, or the allocation fails outright. 3. **Locality degrades.** Objects allocated together in time end up scattered across the address space, so they land on different cache lines and different pages. A compacting collector, by contrast, tends to place objects allocated together near each other, which improves cache behaviour on later traversals. 4. **Free-space bookkeeping costs memory and time.** Free lists, coalescing, and size classes are all overhead that a bump allocator does not pay. ## Where this sits among the alternatives Mark-compact keeps the same marking phase but replaces sweeping with a relocation pass that slides survivors together, eliminating fragmentation at the cost of moving objects and rewriting every reference to them. Copying collection avoids the sweep entirely by moving survivors into a fresh space and reclaiming the source wholesale - fast when few objects survive, but requiring reserve space to copy into. So the three-way trade is: mark-sweep pays in fragmentation and allocation complexity but never moves anything; mark-compact pays relocation cost to get bump allocation back; copying pays memory reserve to get both cheap collection and bump allocation, and is efficient only when survival rates are low. ## The pattern to remember Mark cost tracks live data; sweep cost tracks heap size; fragmentation is the structural consequence of not moving anything. Every practical refinement - free-list size classes, deferred sweeping, occasional compaction - exists to soften one of those three facts.
- Why does the collector mark live objects rather than searching for dead ones?Unreachability cannot be observed locally - there is no way to look at an object and see that nothing points to it without knowing the whole reference graph. Tracing from the roots computes reachability, and everything not reached is dead by definition. A useful side effect is that the expensive phase scales with live data, so a heap that is mostly garbage is cheap to trace.
- Mark cost scales with live data and sweep cost with heap size. What does that imply for a large, mostly-empty heap?Marking will be fast because there is little live data to traverse, but sweeping still has to walk the entire address range, so the whole-heap component dominates. This is why real collectors avoid naive full sweeps - using lazy or incremental sweeping, per-region reclamation, or copying strategies that reclaim whole areas without inspecting the dead objects at all.
A librarian tags every book still on loan-record, then removes all untagged books from the shelves. The shelves are never re-shelved, so what remains is gaps of random width - and a boxed encyclopedia set may not fit anywhere even though half the shelf space is empty.
saying these in an interview costs you the question
- Saying the collector finds and frees garbage objects directly, instead of marking live objects and treating the remainder as garbage.
- Claiming mark-sweep compacts or defragments the heap.
- Thinking sweep cost is proportional to the number of dead objects rather than to heap size.
- Believing fragmentation only wastes memory, missing that it slows allocation and can make large allocations fail.
- Confusing mark-sweep with reference counting, which frees on the last reference drop and cannot reclaim cycles.