skip to content

Collector Selection

Choosing a collector for the workload: throughput-oriented parallel, region-based, fully concurrent low-pause designs, or a no-op collector for short batch jobs, along with the pause-time target you set. Interviewers treat it as a judgement question about throughput versus latency versus footprint, not a recall question.

on this pageshow

questions

6

Which garbage collector does a modern HotSpot JVM use if you specify nothing, and what makes it pick a different one on its own?

level: juniorimportance: must knowfreq 55%

answer

  1. JDK 9+: G1 by default; JDK 8: Parallel
  2. Server-class test: ~2+ CPUs and ~1792 MB → G1, else Serial
  3. Ergonomics also derives heap size and thread counts
  4. Containers: cgroup limits are the input — small pod can get Serial
  5. CMS removed in JDK 14

basics

~20 s

On JDK 9 and later the default is G1 on any server-class machine (roughly two or more CPUs and enough memory). On a smaller machine or container, JVM ergonomics falls back to Serial. On JDK 8 the default was the Parallel collector.

solid answer

~50 s

Since JDK 9, HotSpot's default is **G1**, selected by *ergonomics*: at startup the JVM inspects the available CPUs and memory, and if the machine looks server-class — historically two or more available processors and roughly 1792 MB or more of memory — it chooses G1 and a heap sized as a fraction of available memory. Below that threshold it picks **Serial**, which has the smallest footprint and no parallel overhead. On JDK 8 the equivalent default was **Parallel**, which is why a JDK 8 → 17/21 upgrade changes GC behaviour even with no flag changes. Two practical consequences. First, in containers the JVM reads the cgroup CPU and memory limits, so a tightly limited container can silently land on Serial — a real surprise if you assumed G1. `-XX:+PrintFlagsFinal` or `-Xlog:gc` at startup tells you which collector actually started. Second, an explicit `-XX:+UseG1GC` / `-XX:+UseParallelGC` / `-XX:+UseZGC` / `-XX:+UseSerialGC` overrides ergonomics entirely — pin it when you care.

go deeper

for a junior

State that G1 is the default from JDK 9 (Parallel on 8), that ergonomics chooses based on CPUs and memory, and that Serial is the small-machine fallback.

for a middle

Add the container angle — cgroup limits feed ergonomics — and how to verify the running choice.

for a senior

Connect the default change to an upgrade's observed pause and throughput shift, and pin collector and heap explicitly in a fleet rather than relying on ergonomics.

for a principal

Treat ergonomic defaults as policy risk: an implicit choice that varies with instance shape is a source of fleet inconsistency, so standardise it in the base image.

## Ergonomics: the JVM configures itself HotSpot does not start with fixed defaults. At startup it probes the environment — number of available processors, amount of physical (or cgroup-limited) memory — and derives a collector, heap size, generation sizes and thread counts from it. This is called *ergonomics*, and its purpose is that a JVM launched with no flags at all behaves sensibly on both a laptop and a 64-core server. ## What it chooses today On JDK 9 and later, if the machine is classified as **server-class**, ergonomics selects **G1** and sets the maximum heap to a fraction of available memory (commonly one quarter, via `MaxRAMPercentage`). The historical server-class test is at least two available processors and roughly 1792 MB or more of memory; below it, ergonomics selects **Serial**, whose single-threaded collector has the lowest startup cost, the smallest footprint and no coordination overhead — the right answer on a one-CPU container or a tiny device. This matters far more in the container era than it did on bare metal. A pod limited to one CPU, or a small memory limit, can land on Serial even though the operators believe the fleet runs G1. The default heap ceiling likewise comes from the *container's* limit rather than the host's, since HotSpot became cgroup-aware. Both are silent: nothing warns you. ## What changed across versions - **JDK 8**: default **Parallel** (throughput-oriented, stop-the-world young and full collections). CMS was available for lower pauses. - **JDK 9**: default becomes **G1**. - **JDK 14**: **CMS removed**. Startup scripts carrying `-XX:+UseConcMarkSweepGC` fail or warn. - **JDK 11+**: **ZGC** and **Epsilon** appear (initially experimental), **Shenandoah** appears in OpenJDK-based builds. - **JDK 15**: ZGC and Shenandoah become production-ready. - **JDK 21**: generational ZGC arrives alongside the original single-generation mode. - **JDK 23 onward**: ZGC runs in generational mode by default; the non-generational mode was deprecated and then removed. The practical upshot: an upgrade from JDK 8 changes the collector, and therefore the pause profile, even when no GC flag changes. Applications tuned for Parallel's throughput often see slightly lower throughput and much better pause behaviour on G1, and teams that never noticed the switch have blamed the new JDK for both. ## Selecting explicitly ``` -XX:+UseSerialGC // single-threaded, smallest footprint -XX:+UseParallelGC // throughput-oriented, parallel stop-the-world -XX:+UseG1GC // region-based, incremental, pause-target driven (default) -XX:+UseZGC // fully concurrent, very low pause -XX:+UseShenandoahGC // fully concurrent, very low pause (OpenJDK-based builds) -XX:+UseEpsilonGC // no-op; requires -XX:+UnlockExperimentalVMOptions ``` An explicit flag disables the ergonomic choice for the collector but not the rest of ergonomics — heap and thread counts are still derived unless you also pin them. ## Verifying what actually started Never assume; check. `-Xlog:gc` prints the collector's own vocabulary from the first collection (`G1 Evacuation Pause` versus a Serial/Parallel `Allocation Failure`), `-Xlog:gc+init` (where available) prints the startup configuration, and `java -XX:+PrintFlagsFinal -version | grep Use.*GC` shows which `Use...GC` flag ended up true. In a container, also confirm the CPU and memory the JVM believes it has, because that is the input ergonomics used. ## Why juniors get asked this It is a cheap screen for two things: whether the candidate knows the JVM configures itself rather than using constants, and whether they have actually operated a JVM across a version upgrade. "G1 by default since JDK 9, Parallel on 8, Serial on small machines, and ergonomics decides" is a complete answer at this level.

  • A team upgrades a service from JDK 8 to JDK 21 with unchanged flags and reports slightly lower throughput but far fewer long pauses. Why?
    The default collector changed from Parallel to G1. Parallel maximises throughput with stop-the-world collections whose duration scales with the heap, while G1 does part of its work concurrently and targets a pause goal, paying a few percent of throughput for it. The observed profile is exactly the expected consequence of the default change, not a regression in the JDK.
  • How do you confirm which collector a running JVM actually chose?
    Enable `-Xlog:gc` and read the collector-specific vocabulary in the first collection lines, or run `java -XX:+PrintFlagsFinal -version` and inspect which `Use...GC` flag is true. In a container, also check what CPU and memory limits the JVM detected, since those are the inputs ergonomics used to decide.

saying these in an interview costs you the question

  • Saying the default is still Parallel on modern JDKs, or that CMS is the low-pause default.
  • Assuming the collector is fixed and the JVM does not adapt to the machine it starts on.
  • Not realising a small container can land on Serial via ergonomics.
  • Believing an explicit collector flag also pins heap size and thread counts.

context

open as a page

When would you choose HotSpot's throughput-oriented Parallel collector (-XX:+UseParallelGC) over G1, and what do you give up by doing so?

level: middleimportance: must knowfreq 60%

basics

~20 s

Choose Parallel when total work per unit time matters and pauses do not — batch jobs, ETL, offline analytics, benchmarks. It usually delivers a few percent more throughput because it does no concurrent work and no write barriers. You give up pause control: its collections stop everything for a time that scales with the heap.

open as a page

When is a fully concurrent low-pause collector such as ZGC or Shenandoah the right choice for a JVM service, and what does it cost you compared with G1?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Choose them when a strict tail-latency SLO must hold on a large heap — pauses stay sub-millisecond to low-millisecond largely independent of heap size, because marking and relocation run concurrently. You pay barrier overhead on application code, extra CPU for concurrent work, and more heap headroom.

open as a page

What does the JVM flag -XX:MaxGCPauseMillis actually do, and what happens if you set it to 5 milliseconds on a G1 heap?

level: middleimportance: should knowfreq 45%

basics

~20 s

It is a soft goal, not a guarantee. G1 uses it to size each collection set: to hit a smaller target it collects less per pause, so pauses become shorter but more frequent, throughput drops, and old-generation reclamation can fall behind. An unrealistic 5 ms mostly costs throughput without delivering 5 ms.

open as a page

You own a fleet of JVM services with mixed workloads — request-serving APIs, streaming consumers, nightly batch jobs. How do you decide which garbage collector each should run, and how do you validate the decision?

level: principalimportance: should knowfreq 40%

basics

~20 s

Classify each service by its governing metric — tail latency, throughput, or footprint — then map: latency SLO plus a large heap goes to a concurrent collector, batch to a throughput collector, everything else to the pause-targeting default. Validate by running the real workload under load with GC logging and comparing pause percentiles, throughput and CPU.

open as a page

HotSpot ships a no-op garbage collector, Epsilon (-XX:+UseEpsilonGC), which allocates memory but never reclaims it. What is it actually for?

level: middleimportance: nice to knowfreq 20%

basics

~20 s

Epsilon allocates and never collects; when the heap is exhausted the JVM exits with an OutOfMemoryError. It exists for performance testing where GC must be excluded, for measuring an application's true allocation footprint, for last-drop latency experiments, and for very short-lived processes that finish before the heap fills.

open as a page