skip to content

JVM Warmup And Daemon Heap

How a persistent daemon amortizes JVM startup and JIT warmup across builds, and how heap and metaspace sizing affect throughput and GC pauses. Asked when a build is slow in ways no task-level fix explains.

on this pageshow

questions

5

Why is the second Gradle build in a session usually noticeably faster than the very first one, even when nothing about the project has changed?

level: juniorimportance: must knowfreq 60%

answer

  1. first build pays JIT + class-load
  2. interpreter → C1/C2 compiles hot methods
  3. daemon keeps warm JVM alive
  4. CI cold daemon per job
  5. warmup amortized across builds

basics

~10 s

Gradle runs in a long-lived background JVM (the daemon). The first build pays JVM startup and HotSpot JIT warmup; later builds reuse that already-warm JVM, so they skip those costs and run faster.

solid answer

~40 s

Gradle executes inside a persistent daemon JVM that survives between invocations. The **first** build in a fresh daemon pays one-time costs: booting the JVM, loading and verifying Gradle's classes, and — crucially — running interpreted bytecode until HotSpot's JIT compiler profiles hot methods and compiles them to optimized native code. Subsequent builds hit an **already warmed** JVM: classes are loaded, the JIT has produced fast compiled code for Gradle's core paths, and the GC has settled into a steady state. So the marginal cost of the second build is mostly the actual work, not interpreter/warmup overhead. This is why CI (which often gets a cold daemon per job) can look slower per build than a developer's machine, and why keeping the daemon alive matters for throughput.

code

bash · 3 lines
bash
./gradlew help     # cold daemon: JVM boot + HotSpot warmup
./gradlew help     # warm daemon: skips both, much faster
./gradlew --status # list live daemons

go deeper

for a junior

Know that Gradle reuses a long-lived JVM so the second build skips startup and warmup.

for a middle

Explain the two costs (class-load and JIT interpreter→compiled transition) and that the daemon retains both across builds.

for a senior

Connect warmup to CI behavior (cold daemon per job) and separate it cleanly from cache/up-to-date avoidance of task work.

for a principal

Reason about org-wide CI cost: trade-offs of daemon reuse vs. ephemeral runners, and whether warmup cost justifies persistent build infrastructure.

## The problem: JVMs start slow Every JVM process begins in a **cold** state. Two costs dominate the first build: 1. **Process + class-load cost** — spawning the JVM, then loading, verifying, and initializing thousands of Gradle and plugin classes. 2. **JIT warmup** — HotSpot first **interprets** bytecode. As methods execute, it counts invocations/loop back-edges and, once they pass a threshold, the **C1/C2 just-in-time compilers** compile those *hot* methods into optimized native code (often after gathering profiling data, with speculative optimizations that can later deoptimize). Until that happens, code runs in the slow interpreter. ## How the daemon amortizes this Gradle does its real work inside a **daemon** — a background JVM that **stays alive between builds**. The first build absorbs all the warmup. The daemon then keeps: - loaded/initialized classes, - JIT-compiled hot methods (configuration evaluation, dependency resolution, task graph wiring, etc.), - a GC heap that has reached steady state. The **next** invocation reuses that warm process, so it skips startup and re-warmup. Empirically the difference is large for short builds — a cold build can spend a meaningful fraction of its time just becoming fast. ## Why CI feels different CI runners frequently start a **fresh daemon per job** (clean container, `--no-daemon` historically common). Each job therefore pays full warmup, which is one reason a build that's snappy locally looks heavier on CI. Reusing daemons (or accepting cold cost) is a deliberate trade-off. ## What this is NOT This warmup amortization is about CPU/throughput of the *build logic itself*. It is distinct from up-to-date checks and the build cache, which avoid re-running *task work*. A warm daemon makes the parts that always run (configuration, graph building) cheap; the cache makes task execution cheap. They stack. ```bash # First call boots + warms a daemon; second reuses it. ./gradlew help # cold: JVM start + JIT warmup ./gradlew help # warm: reuses the live daemon # Inspect live daemons and their state ./gradlew --status ```

  • Does the build cache make JVM warmup irrelevant?
    No. The cache avoids re-running task *work*, but configuration, task-graph wiring, and dependency resolution still run every build — a warm JIT makes those cheap. The two optimizations are complementary, not substitutes.
  • Why might a CI build not show this speedup?
    CI often gets a fresh container and a cold daemon (or runs `--no-daemon`), so every job re-pays startup and JIT warmup; there's no prior warm process to reuse.

Like a delivery van: the first trip includes warming the cold engine; later trips that morning start instantly because the engine is already running.

saying these in an interview costs you the question

  • Claiming the speedup comes only from the build cache or up-to-date checks (it also comes from JIT/class-load warmup).
  • Saying Gradle 'compiles your project faster the second time' — the warmup is about Gradle's own JVM, not your compiled output.

context

open as a page

How does the daemon's heap and metaspace sizing affect build throughput, and what symptom tells you the JVM is the bottleneck?

level: middleimportance: must knowfreq 48%

basics

~20 s

If the daemon heap is too small, the JVM spends excessive time in garbage collection, stealing CPU from the build and slowing it down. Adequate heap and metaspace let work proceed with minimal GC pauses.

open as a page

A teammate claims keeping the daemon alive 'doesn't help much.' How would you measure the real throughput impact of warmup versus a cold start for your project?

level: middleimportance: should knowfreq 28%

basics

~10 s

Run the same build several times in one daemon and record each duration; then run it again with the daemon killed/disabled. Compare the cold run against the warm steady-state to quantify the warmup savings.

open as a page

On CI, builds frequently start with a cold daemon. What performance cost does that impose, and how do you reason about whether to keep daemons warm there?

level: seniorimportance: should knowfreq 38%

basics

~10 s

A cold daemon re-pays JVM startup and JIT warmup every job, so CI builds lose the amortization a developer enjoys. Keeping daemons warm helps long-running runners but conflicts with ephemeral, clean-container CI.

open as a page

Explain how HotSpot's tiered JIT compilation produces the warmup curve a Gradle daemon experiences, and why a long-lived daemon eventually reaches peak throughput.

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

HotSpot first interprets, then C1-compiles hot methods quickly, gathers profiles, and finally C2-compiles the hottest methods to highly optimized code. Over several builds the daemon converges on this fast C2 code, reaching peak speed.

open as a page