skip to content

What garbage collector does a GraalVM native image use by default, and why does that choice fit the native use case?

level: middleimportance: should knowfreq 40%

answer

  1. Native default = Serial GC
  2. single-threaded, stop-the-world, smallest footprint
  3. G1 = Oracle GraalVM, --gc=G1, more RAM, more throughput
  4. Epsilon = no-op, only ultra short-lived
  5. trap: NOT G1 by default

basics

~20 s

By default a native image uses the Serial GC — a simple, single-threaded, stop-the-world collector. It has the lowest memory overhead, which matches native's goal of a small footprint, though it can limit throughput and cause longer pauses under heavy load.

solid answer

~40 s

The default GC in GraalVM native image is **Serial GC**: single-threaded, generational, fully stop-the-world. It's chosen because it has the smallest memory and code footprint, aligning with native's core value proposition — low RSS and fast startup — and it works well for small heaps and short-lived or lightly-loaded processes (serverless, CLIs). Its downsides are lower throughput and longer stop-the-world pauses under allocation-heavy or large-heap workloads, since collection isn't parallel or concurrent. For throughput-sensitive services, **Oracle GraalVM** offers **G1** (`--gc=G1`) at the cost of more memory, and **Epsilon** (a no-op collector) exists for ultra-short-lived tasks that never need to reclaim memory. So the default optimizes for footprint; you opt into G1 when you need throughput.

code

kotlin · 9 lines
kotlin
// build.gradle.kts — opt into G1 for a throughput-oriented native service (Oracle GraalVM)
graalvmNative {
    binaries {
        named("main") {
            buildArgs.add("--gc=G1") // default without this is Serial GC
            // Epsilon (no-op GC) for ultra short-lived tasks: buildArgs.add("--gc=epsilon")
        }
    }
}

go deeper

for a junior

Know the default is Serial GC — simple and low-memory.

for a middle

Explain the footprint-vs-throughput trade and name G1/Epsilon alternatives.

for a senior

Tie GC choice to workload and to the Oracle-vs-Community edition constraint.

for a principal

Fold GC/edition choices into capacity planning and licensing/cost decisions for the fleet.

**What a GC does.** A garbage collector reclaims heap memory no longer referenced. Collectors trade off three things: memory footprint, throughput (useful work vs GC time), and pause times (how long the app stops for GC). **Serial GC — the native default.** GraalVM native image defaults to **Serial GC**: a *generational* collector (young + old generations) that is *single-threaded* and *fully stop-the-world* (the whole application pauses during a collection). It is deliberately the default because it has the **smallest footprint** — minimal collector code and metadata, small heaps — which reinforces native's headline benefits of low RSS and fast startup. For small heaps, short-lived processes (a serverless invocation), or lightly-loaded services, Serial GC is entirely adequate and keeps memory tiny. **Its limits.** Because collection is single-threaded and stop-the-world, under **allocation-heavy** load or with **large heaps**, Serial GC delivers lower throughput and longer pauses than parallel/concurrent collectors. This compounds the peak-throughput trade-off native already carries. **Alternatives.** - **G1 (`--gc=G1`)** — available in **Oracle GraalVM** (not Community Edition). A parallel, partly-concurrent, region-based collector that scales to larger heaps with better throughput and shorter pauses, at the cost of higher memory overhead. Choose it for long-running, throughput-oriented native services. - **Epsilon (`--gc=epsilon`)** — a *no-op* collector that allocates but never reclaims. Only viable for very short-lived tasks whose total allocation fits in the heap (e.g., a function that runs, produces output, and exits). Using it for a long-running service will exhaust the heap. **How to set it.** Pass a build argument via the Native Build Tools, e.g. `buildArgs.add("--gc=G1")` in the `graalvmNative` block, or `-H:+UseSerialGC` style flags. Heap sizing (`-Xmx`, or `-XX:MaximumHeapSizePercent`) still applies at runtime. **Gotcha / interview trap.** Candidates often assume native images default to G1 (the modern OpenJDK default) — they do **not**; the native default is **Serial GC**, and G1 for native is an **Oracle GraalVM** feature. Getting the edition dependency right signals real experience.

  • Is G1 available in GraalVM Community Edition for native image?
    No. G1 for native image is an Oracle GraalVM feature. Community Edition native images are limited to Serial GC (and Epsilon), which is a real factor when planning throughput-heavy native workloads.
  • When would Epsilon (no-op GC) be safe?
    Only for very short-lived processes whose total allocation fits within the configured heap and that exit before running out of memory — e.g., a one-shot batch task or function invocation. Never for a long-running server.

saying these in an interview costs you the question

  • Saying native defaults to G1 like modern OpenJDK
  • Assuming G1 is free in Community Edition
  • Treating Epsilon as usable for long-running services

context