skip to content

Serial Collector

A single-threaded, fully stop-the-world collector that is still the right answer for small heaps, single-core containers, and short-lived processes. Interviewers use it as the baseline the other collectors are compared against, and as a check on whether you tune for the workload rather than by reputation.

on this pageshow

questions

5

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%

answer

  1. -XX:+UseSerialGC — one thread, always STW
  2. DefNew = copying young; MarkSweepCompact = sliding old
  3. Pause tracks live set, not garbage
  4. Compaction ⇒ contiguous free block, bump allocation
  5. Ergonomics may pick it silently on small machines

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.

solid answer

~50 s

The Serial collector is HotSpot's single-threaded, fully stop-the-world collector, selected with `-XX:+UseSerialGC`. - **Young generation ("DefNew")**: a copying collector. Objects are bump-allocated in eden; on a minor GC one thread traces live objects from the roots and copies them into the empty survivor space, ageing them. Objects that survive enough copies (`-XX:MaxTenuringThreshold`, default 15) or that no longer fit are promoted into the old generation. Eden and the from-survivor are then empty wholesale, so allocation stays a pointer bump. - **Old generation ("MarkSweepCompact")**: a sliding mark-compact collector. It marks live objects, computes each survivor's new address, rewrites every reference to it, then slides the live objects to one end of the space. The result is one contiguous free block, so no fragmentation. Everything runs in one thread inside one pause, so pause length tracks the amount of *live* data, not the amount of garbage. It is the right choice for small heaps, single-CPU or CPU-capped containers, and short-lived processes.

code

text · 3 lines
text
$ java -XX:+UseSerialGC -Xlog:gc -jar app.jar
[0.412s][info][gc] GC(0) Pause Young (Allocation Failure) 33M->5M(123M) 4.212ms
[6.905s][info][gc] GC(7) Pause Full (Allocation Failure) 118M->41M(123M) 96.803ms

go deeper

for a junior

Recall the shape: one thread, full stop-the-world, copying young plus compacting old, -XX:+UseSerialGC.

for a middle

Add mechanics: eden/survivor copying with tenuring, why compaction avoids fragmentation, and that pause time follows the live set.

for a senior

Frame it as a deliberate footprint-and-simplicity choice, and be ready to say which workloads it suits and where it breaks down.

for a principal

Position it in the collector portfolio: the baseline against which barrier cost, worker-thread CPU, and metadata footprint of every other collector should be justified.

## What "serial" means here Collectors differ on two axes: how many threads do the GC work, and whether application threads ("mutators") keep running during GC. The Serial collector sits at the simplest corner of both: **one** GC thread, and **zero** mutator progress while it runs. There is no concurrent phase, no parallel worker pool, no incremental work. Every collection is a single pause during which one thread does all the tracing and all the moving. That simplicity is the whole point. It has the least code, the least metadata, the fewest barriers on the application's hot paths, and the smallest native-memory footprint of any HotSpot collector. ## Young-generation collection: copying The young generation is laid out as eden plus two equal survivor spaces ("from" and "to"); only one survivor is in use at a time. Allocation is a pointer bump in eden. When eden fills, a **minor GC** (logged as an "Allocation Failure") begins: 1. All threads are brought to a safepoint. 2. The single GC thread scans the roots — thread stacks, static fields, JNI handles — plus the recorded old-to-young references, and traces the live object graph inside the young generation. 3. Every live object it finds is **copied** out: into the empty "to" survivor if it fits and is still young, or into the old generation if its age has reached the tenuring threshold or the survivor space is full. 4. Each copied object's header records an incremented age; the forwarding address of the copy is left behind so other references can be redirected to it. 5. Eden and the old "from" survivor are then declared empty in one step, the survivors swap roles, and the mutators resume. Because a copying collector touches only the *live* objects and then frees the whole region at once, its cost is proportional to survivors, not to garbage. A young generation where almost everything dies is collected almost for free — which is why minor GCs stay in the low milliseconds even single-threaded, as long as the survivor set is small. ## Old-generation collection: mark-compact When the old generation fills (or promotion fails), Serial runs a **full GC** over the entire heap using a sliding mark-compact algorithm. It marks all reachable objects, computes where each will land after compaction, rewrites every reference in the heap and in the roots to the new addresses, and then slides live objects down so they sit contiguously at the bottom of the space. Compaction rather than sweeping means the free space is one contiguous block afterwards, so subsequent allocation and promotion are simple pointer bumps and fragmentation never accumulates. The price is that all four passes run in one thread inside one stop-the-world pause, and their cost is proportional to the *live set* and the heap size being walked. ## Selecting it `-XX:+UseSerialGC` chooses it explicitly. HotSpot may also choose it for you: on a machine (or container) that fails the "server-class" test — roughly, at least two available processors and at least ~1792 MB of memory — ergonomics fall back to Serial instead of the default G1. So a JVM in a one-CPU container can be running Serial without anyone having configured it. Confirm with `-Xlog:gc` output or `java -XX:+PrintFlagsFinal -version | grep UseSerialGC`. ## What you gain and what you give up **Gain:** the smallest metadata footprint (no per-region tables, no marking bitmaps sized to the heap, no per-worker structures), the cheapest write barrier (a simple card mark), fast JVM startup, and no GC threads competing with the application for scarce CPU. On a small heap with a small live set, full GCs can complete in tens of milliseconds. **Give up:** any pause-time scalability. Pause time grows with the live set, and there is no second thread to shorten it. On a multi-gigabyte heap a full GC can take seconds, so Serial is unsuitable for latency-sensitive services with large live data. It is a deliberate choice for small, dense, or short-lived JVMs — not a general-purpose default.

  • Why does a copying young collector not suffer from fragmentation?
    It evacuates every live object out of the region into a fresh, contiguous area, then reclaims the source region as one whole block. Because survivors are packed side by side at their destination, free space is always contiguous and allocation stays a pointer bump. The cost is that you need spare space to copy into, so part of the young generation is always reserved.
  • If Serial is single-threaded, what makes its young collections still fast enough for small services?
    A copying collector's work is proportional to the number of surviving objects, not to the size of eden or the volume of garbage. In typical workloads most young objects die before the first collection, so a minor GC copies very little and finishes in a few milliseconds even with one thread.

One librarian closing the whole library to reshelve: nobody browses while they work, but there is no coordination overhead and nothing gets lost between helpers.

saying these in an interview costs you the question

  • Saying Serial is only for the young generation, or that it never compacts
  • Claiming its pause time depends on how much garbage there is (it depends on the live set)
  • Assuming it is dead legacy code — HotSpot still ships it and ergonomics still select it in small containers
  • Confusing it with a collector that runs concurrently with the application; Serial has no concurrent phase

context

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

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

You operate several hundred small JVM services on shared nodes, each with a heap under 512 MB. How would you decide whether to standardize the fleet on the single-threaded stop-the-world collector, and what would you measure to defend the decision?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Split the fleet by SLO class rather than deciding once. Measure per-service live set, full-GC pause distribution, container RSS, and CPU-seconds per request. Standardize the single-threaded collector as the default for low-SLO and short-lived services, with an explicit, reviewed override for latency-critical ones.

open as a page