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?
answer
- first build pays JIT + class-load
- interpreter → C1/C2 compiles hot methods
- daemon keeps warm JVM alive
- CI cold daemon per job
- warmup amortized across builds
basics
~10 sGradle 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 sGradle 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./gradlew help # cold daemon: JVM boot + HotSpot warmup
./gradlew help # warm daemon: skips both, much faster
./gradlew --status # list live daemonsgo deeper
Know that Gradle reuses a long-lived JVM so the second build skips startup and warmup.
Explain the two costs (class-load and JIT interpreter→compiled transition) and that the daemon retains both across builds.
Connect warmup to CI behavior (cold daemon per job) and separate it cleanly from cache/up-to-date avoidance of task work.
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.