skip to content

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