skip to content

Garbage Collection

Garbage collection from the algorithms up: marking, copying, compaction, safepoints and the collectors you can choose. Asked in any round on latency or footprint, since collector choice visibly changes production.

on this pageshow

questions

page 1 of 2

Describe how a mark-sweep garbage collection algorithm works, phase by phase, and explain the characteristic problem it leaves behind in the heap.

level: juniorimportance: must knowfreq 68%

answer

  1. mark from roots, sweep the rest
  2. mark cost ~ live data; sweep cost ~ heap size
  3. non-moving: addresses stable, no pointer fixup
  4. fragmentation -> free list, not bump pointer
  5. big allocation fails despite free bytes

basics

~20 s

Mark: 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 s

Mark-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

for a junior

Name the two phases, say that liveness is decided by tracing from roots, and state that fragmentation is the leftover problem because nothing moves.

for a middle

Add the cost model - marking scales with live data, sweeping with heap size - and explain the free-list allocation consequence versus bump-pointer allocation.

for a senior

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.

for a principal

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.

context

open as a page

What is the Serial garbage collector in the HotSpot JVM, how does it collect the young and the old generation, and how do you turn it on?

level: juniorimportance: must knowfreq 52%

basics

~20 s

HotSpot's simplest collector: one GC thread, and every collection stops all application threads. The young generation is collected by copying live objects into a survivor space; the old generation by a mark-compact pass. Enable it with -XX:+UseSerialGC.

open as a page

What does a 'stop-the-world' pause mean inside a JVM, and why does a garbage collector need application threads suspended at all?

level: juniorimportance: must knowfreq 58%

basics

~20 s

A stop-the-world pause is an interval where the JVM suspends every application thread so the runtime can do work that needs a stable view of the heap and of thread state — for example scanning thread stacks for references. Application code makes no progress for the duration.

open as a page

Concurrent garbage collectors describe objects as white, gray, or black while tracing. Define each colour, and explain what the collector has established once no gray objects remain.

level: middleimportance: must knowfreq 52%

basics

~20 s

White means not yet reached, gray means reached but its outgoing references not yet scanned, black means reached and fully scanned. When no gray objects remain, tracing is complete: everything reachable is black and every remaining white object is unreachable and can be reclaimed.

open as a page

The Garbage-First collector is named for the order in which it reclaims memory. Explain what 'garbage first' means in practice and how the collector chooses which heap regions go into the next collection set.

level: middleimportance: must knowfreq 55%

basics

~20 s

G1 tracks how much live data each region holds. For a given pause it collects all young regions plus, in mixed collections, the old regions with the least live data - the most garbage - because evacuation cost scales with live bytes, so those regions give the most free space per millisecond spent.

open as a page

The HotSpot Garbage-First (G1) collector divides the Java heap into regions instead of contiguous young and old spaces. Explain how that layout works and what eden, survivor, old, and humongous regions mean.

level: middleimportance: must knowfreq 62%

basics

~20 s

G1 splits the heap into equal-sized regions (a power of two, 1-32 MB, roughly 2048 of them). Each region is tagged at runtime as eden, survivor, old, or humongous. A generation is the current set of regions with that tag, not a contiguous address range.

open as a page

Explain how a copying (semi-space) garbage collection algorithm reclaims memory, why its collection cost does not depend on how much garbage there is, and what it gives up in exchange.

level: middleimportance: must knowfreq 60%

basics

~20 s

The space is split in two. Live objects are copied from the in-use half into the empty half, references are updated to the new addresses, and the source half is then reclaimed entirely - dead objects are never touched. Cost scales with survivors only; the price is holding half the space in reserve.

open as a page

HotSpot's Parallel collector (enabled with -XX:+UseParallelGC) is described as a "throughput collector." What does that mean concretely about how it does its work, and what does it give up in exchange?

level: middleimportance: must knowfreq 55%

basics

~20 s

Every phase is stop-the-world but executed by many GC threads at once, minimising total time spent collecting and maximising the share of time the application runs. The cost: full stops whose length grows with live-set size, and no collection work overlapping the application.

open as a page

What is the Shenandoah garbage collector in HotSpot, and what does it mean that its pause times are said to be independent of heap size?

level: middleimportance: must knowfreq 34%

basics

~20 s

A region-based HotSpot collector that does both marking and compaction (evacuation) concurrently with the running application. Its stop-the-world pauses only scan roots and do bookkeeping, so they scale with the root set, not with heap size or live data. Enabled with -XX:+UseShenandoahGC.

open as a page

What events cause a JVM to start a young (minor) collection, and what causes it to fall back to a full collection of the whole heap?

level: middleimportance: must knowfreq 58%

basics

~20 s

A young collection is triggered when allocation cannot be satisfied from the young space — Eden is full. A full collection happens when the old generation cannot accept what must be promoted or has no room, when the class-metadata area hits its threshold, or when something explicitly requests one; in modern collectors it is mostly a fallback after concurrent work fails to keep up.

open as a page

What is a JVM safepoint, and why can an application thread only be suspended for a VM operation when it has reached one?

level: middleimportance: must knowfreq 50%

basics

~20 s

A safepoint is a point in a thread's execution where the runtime knows exactly where every object reference that thread holds is located, because the compiler recorded a map for that point. Threads can only be paused there; suspending at an arbitrary instruction would leave references the collector cannot find or update.

open as a page

The ZGC collector in HotSpot advertises garbage-collection pauses under a millisecond that do not grow with heap size. Which parts of its collection cycle run concurrently with application threads, and what work is still done inside a stop-the-world pause?

level: middleimportance: must knowfreq 45%

basics

~20 s

ZGC marks, relocates and remaps concurrently with running threads. Only three short root-oriented pauses remain: mark start, mark end, relocate start. Their cost scales with the number of thread roots, not with heap or live-set size, so pauses stay sub-millisecond.

open as a page

HotSpot's Concurrent Mark-Sweep collector reclaimed old-generation space without compacting it. Explain what that caused over time, and what a "concurrent mode failure" in its logs meant for the application.

level: seniorimportance: must knowfreq 48%

basics

~20 s

Sweeping in place left free space scattered across free lists, so the old generation fragmented. Eventually a promotion needed a contiguous block bigger than any free chunk even though total free bytes were ample. That triggered a fallback to a single-threaded stop-the-world mark-sweep-compact full collection — a pause of many seconds, the exact thing CMS existed to avoid.

open as a page

While a garbage collector traces the heap concurrently, application threads keep rewriting references. Describe the exact sequence of mutations that can cause a live object to be missed, and state the two conditions that must both hold for it to happen.

level: seniorimportance: must knowfreq 44%

basics

~20 s

An object is lost when a mutator stores a reference to an unscanned (white) object into an already-scanned (black) object, and then deletes every remaining path to it from unscanned objects. The collector never revisits the black object, finds no other route, and treats the live object as garbage.

open as a page

Compare snapshot-at-the-beginning and incremental-update as correctness strategies for concurrent garbage-collection marking: what does each one's write barrier record, and how does the choice affect floating garbage and end-of-cycle work?

level: seniorimportance: must knowfreq 40%

basics

~20 s

A snapshot-at-the-beginning barrier saves the overwritten (old) reference before a store, so anything live when marking began stays marked - producing floating garbage but predictable termination. An incremental-update barrier records the newly stored reference or re-greys the target, marking a more current picture at the cost of rescanning.

open as a page

The Shenandoah collector relocates objects while application threads are running. Explain the load reference barrier it uses, the Brooks forwarding pointer it replaced, and what invariant they enforce.

level: seniorimportance: must knowfreq 29%

basics

~30 s

A load reference barrier is JIT-inserted code that runs when a reference is loaded from the heap: if the referent lives in a region being evacuated, the barrier resolves or copies it to its new location and writes the corrected reference back. That enforces the to-space invariant — application threads only ever work with up-to-date copies. It replaced the Brooks pointer, an extra header word every access had to indirect through.

open as a page

Which JVM flag selects HotSpot's Parallel garbage collector, how many collector threads does it use by default, and was it ever the JVM's own default choice?

level: juniorimportance: should knowfreq 32%

basics

~20 s

-XX:+UseParallelGC selects it. Thread count defaults to the number of available processors up to 8, then grows more slowly above that, and is overridable with -XX:ParallelGCThreads. It was the default on server-class machines through JDK 8; from JDK 9 the region-based G1 collector became the default.

open as a page

HotSpot's Concurrent Mark-Sweep collector (CMS) performed most of its old-generation marking while application threads kept running. Walk through the phases of one of its old-generation cycles and say which of them stopped the world.

level: middleimportance: should knowfreq 40%

basics

~20 s

Initial mark (stop-the-world, marks roots), concurrent mark, concurrent preclean and abortable preclean, final remark (stop-the-world, catches what mutation changed), concurrent sweep, concurrent reset. Only initial mark and remark paused the application; young collections were separately stop-the-world. Sweeping reclaimed space in place without compacting.

open as a page

For HotSpot's Parallel collector (-XX:+UseParallelGC), describe what happens during a young collection versus a full collection — which algorithm each phase uses, and why the resulting heap has no fragmentation.

level: middleimportance: should knowfreq 40%

basics

~20 s

A young collection is a parallel copying collection: live objects in eden and the active survivor space are copied to the other survivor space or promoted to the old generation. A full collection is a parallel mark, summary and sliding-compaction pass over the entire heap. Both move objects, so free space stays contiguous.

open as a page

When does HotSpot select the Serial collector automatically instead of the default G1, and how does running inside a container with CPU and memory limits change that decision?

level: middleimportance: should knowfreq 36%

basics

~20 s

If the JVM does not detect a "server-class" machine — roughly at least two available processors and about 1792 MB of memory — ergonomics fall back to Serial. Container limits count: with container support on, cgroup CPU quota and memory limits feed those checks, so a one-CPU container silently gets Serial.

open as a page

HotSpot's Serial collector compacts the old generation rather than leaving a free list behind. Walk through the phases of that mark-compact collection and explain how references are fixed up after objects move.

level: middleimportance: should knowfreq 28%

basics

~20 s

Four passes in one stop-the-world thread: mark live objects from the roots; compute each live object's new address and store it as a forwarding pointer in its header; walk roots and live objects rewriting every reference to the forwarded address; then slide the objects down to those addresses and restore headers.

open as a page

Walk through the phases of one garbage-collection cycle in HotSpot's Shenandoah collector, and state precisely which parts stop the application threads.

level: middleimportance: should knowfreq 26%

basics

~20 s

Init Mark (stop-the-world: scan roots, arm barriers), Concurrent Mark, Final Mark (stop-the-world: drain marking buffers, choose the collection set, evacuate root-referenced objects), Concurrent Cleanup of empty regions, Concurrent Evacuation, Init Update Refs (brief stop), Concurrent Update References, Final Update Refs (stop-the-world: fix remaining roots, recycle regions).

open as a page

Generational ZGC shipped as an option in JDK 21 and became the default in JDK 23. What does splitting a concurrent, colored-pointer collector into young and old generations actually change in its implementation, and which production problem motivated the change?

level: middleimportance: should knowfreq 35%

basics

~20 s

It lets ZGC collect a small young space frequently instead of marking the whole heap each cycle. That required a new store barrier and remembered sets to track old-to-young references. The payoff: much less CPU and heap headroom for a given allocation rate, and fewer allocation stalls.

open as a page

HotSpot's Concurrent Mark-Sweep collector had to begin its concurrent old-generation cycle well before the old generation was actually full. Why could it not simply wait until the space ran out, and what were the costs of starting too early or too late?

level: seniorimportance: should knowfreq 32%

basics

~20 s

Its marking and sweeping ran concurrently with the application, which keeps allocating and promoting the whole time. If the cycle starts when the old generation is already full, there is no space to serve those allocations before it finishes, so the JVM falls back to a long stop-the-world full collection. Starting too early wastes CPU on extra cycles.

open as a page

Walk through the phases of the HotSpot G1 collector's concurrent marking cycle, from what triggers it to what it produces, and explain how its output is used afterwards.

level: seniorimportance: should knowfreq 45%

basics

~20 s

Heap occupancy crossing a threshold starts the cycle: initial mark (piggybacked on a young pause), concurrent root-region scan, concurrent mark, a short stop-the-world remark, then cleanup. It produces per-region live-data counts, frees entirely empty regions immediately, and builds the old-region candidate list for mixed collections.

open as a page

The G1 collector can evacuate a single old heap region without scanning the rest of the heap. Explain the per-region remembered sets that make that possible, how they are kept up to date, and what they cost.

level: seniorimportance: should knowfreq 42%

basics

~20 s

Each region has a remembered set listing where references into it come from, recorded as card-table cards in other regions. Evacuating a region means scanning its remembered set instead of the whole heap. Updates flow from a post-write barrier through dirty-card queues to refinement threads; the cost is memory plus barrier and refinement CPU.

open as a page

Compare mark-compact collection with mark-sweep: what the compaction phase does, what it costs, and why a collector might still use compaction only as a fallback rather than on every cycle.

level: seniorimportance: should knowfreq 48%

basics

~20 s

Both mark identically. Mark-sweep then adds dead space to a free list and leaves survivors in place, so the heap fragments. Mark-compact instead slides survivors together into one contiguous block and rewrites every reference to them, ending fragmented-free but paying a relocation plus pointer-fixup pass over live data.

open as a page

HotSpot's Parallel collector runs with adaptive size policy enabled by default. What exactly is it adapting, in what order of priority, and when would you deliberately disable it with -XX:-UseAdaptiveSizePolicy?

level: seniorimportance: should knowfreq 32%

basics

~20 s

It continuously resizes eden, the survivor spaces and the old generation, and adjusts the tenuring threshold, using measured collection statistics. Goals in priority order: the pause-time goal, then the throughput goal, then minimum footprint. Disable it when you need stable, explicitly-set generation sizes for reproducible behaviour.

open as a page

A Java service runs in a container limited to one CPU and 256 MB of memory. Make the case for and against configuring it with -XX:+UseSerialGC rather than a parallel or concurrent collector.

level: seniorimportance: should knowfreq 34%

basics

~20 s

For: on one core, extra GC threads only time-slice against the application, and Serial has the smallest footprint, cheapest barriers and fastest startup. Against: pauses are single-threaded and scale with the live set, so if live data grows the service gets long, unhelpable full GCs. Decide by measuring live-set size against the latency budget.

open as a page

A service running with -XX:+UseShenandoahGC normally pauses for under 5 ms, but its logs occasionally show a 'Pause Degenerated GC' of several hundred milliseconds and, rarely, a multi-second full GC. What causes these and how would you address them?

level: seniorimportance: should knowfreq 24%

basics

~20 s

The collector lost the race against allocation: the heap ran out before the concurrent cycle finished. It then finishes the remaining work stop-the-world (degenerated GC), or falls back to a full stop-the-world compaction if that also fails. Fix by giving more heap headroom, starting cycles earlier, adding concurrent GC threads, or reducing allocation rate.

open as a page

showing 1–30 of 44