Chapter 25Lesson 03~225 minutes

Gradle Daemon, Workers, Parallelism, File-System Watching, Profiling, and Build Performance: Configuration, Design Choices, and Tradeoffs

Choose Daemon, worker, parallelism, file-watching, memory, and observability policies according to developer iteration patterns, CI topology, resource limits, correctness risk, and the cost of operational complexity.

Performance policyCI throughputMemoryFile watchingTelemetry

Learning objectives

  • Choose Daemon reuse policy for persistent developer machines and ephemeral CI jobs without confusing process lifetime with correctness.
  • Select worker ceilings from measured CPU/RAM/I/O constraints rather than processor count alone.
  • Decide when project parallelism is useful based on graph topology and project decoupling.
  • Choose file-system watching behavior according to local/container/remote filesystem characteristics.
  • Use free local profiling as the default evidence path while treating externally published telemetry as an optional governance decision.
  • Separate build-engine settings from JDK/toolchain, CI scheduler, repository, and operating-system controls.

1. Performance policy is topology-specific

A laptop edit loop, a persistent self-hosted CI agent, and an ephemeral two-vCPU container can all run the same Gradle graph yet need different operational settings. The target is not “maximum parallelism.” It is predictable feedback time per unit of compute/memory without reducing verification or creating hidden machine-specific behavior.

2. Daemon reuse versus ephemeral CI processes

Choice Benefits Costs / risks Evidence to require
Daemon reuse Avoid JVM startup; JIT and in-memory Gradle/plugin caches; Gradle currently recommends for developer and CI use. Idle memory; incompatible JVM args may create multiple Daemons; persistent host needs lifecycle hygiene. Warm/cold profile split, --status, Daemon logs, host memory.
No persistent Daemon Simpler one-job process lifetime; useful for special isolation experiments. Repeated startup/JIT cost; --no-daemon may still need a single-use JVM. Same workload with/without persistence, not anecdote.
Daemon JVM criteria / pinned args Consistent build-engine JVM identity across environments. Upgrade/provisioning policy and possible extra Daemons when criteria differ. Wrapper/JDK/args inventory and CI image policy.

In a short-lived CI container, “persistent Daemon” may mean only reuse across multiple Gradle invocations inside one job. That can still matter. In a long-lived agent, Daemon reuse spans builds and increases the importance of compatible JVM settings and memory governance.

3. More workers versus contention

The default worker ceiling follows detected processors, but the effective CPU quota visible to a container/runtime, test fork counts, process-isolated Worker API jobs, native compilers, and external services all consume the same host. A reasonable tuning process records:

  • effective CPU count/quota and available memory,
  • Gradle --max-workers,
  • test maxParallelForks and any fork-every policy,
  • Worker API process/classloader isolation,
  • other concurrent pipeline steps/containers, and
  • wall-clock, CPU, memory/GC, I/O and failure-rate changes.

If two workers saturate both vCPUs and a third increases GC/scheduling overhead, the third worker is negative capacity.

4. Project parallelism depends on the graph

--parallel is most useful when independent projects expose runnable work. It cannot violate dependsOn relationships. A graph such as :a → :b → :c has little project-level concurrency even on a 32-core machine. Three sibling libraries with independent compile/test paths can benefit much more.

Chapter 21’s structural lesson therefore becomes a performance prerequisite: project boundaries, project coupling, and task dependencies shape the available critical path. Do not remove a real dependency to “unlock” parallelism.

5. Worker API isolation: speed, safety, and memory tradeoffs

Mode Use when Performance implications Safety boundary
noIsolation() Work can safely share Gradle process classes/state allowed by the public API. Lowest process/classloader overhead. Weakest isolation; shared mutable/global libraries can conflict.
classLoaderIsolation() Work needs an isolated classpath but can run in the build JVM. Classloader setup cost; avoids extra process memory. Separates implementation classpath, not OS process/global JVM state.
processIsolation() Library/JVM/system-property needs require separate process. Worker Daemon startup and heap; reusable only within the build session. Strongest isolation of the three for JVM/system state.

Plugin authors should expose deterministic work units and let Gradle schedule them under the worker ceiling. Build users should not retrofit Worker API code into every custom task merely to increase thread counts.

6. File watching: workstation speed versus filesystem reality

Default file watching is attractive for local repeat builds on supported platforms. Container overlays, network mounts, synchronized folders, unusual filesystems, or restricted environments should be measured. If file watching is unsupported or inaccurate for an environment, use --no-watch-fs as a controlled diagnosis—not as a permanent superstition.

Keep project inputs/outputs correct regardless of watching. Chapter 24’s cache/input model remains the source of truth.

7. Heap size is a build-engine setting, not an application setting

org.gradle.jvmargs sizes the Gradle Daemon. It does not set the heap of Test JVMs, JavaExec applications, Worker API process-isolated jobs, or the compiled application. Those have separate controls. A build with a 2 GiB Daemon plus four 1 GiB test forks cannot fit safely inside a 4 GiB CI container even though each individual setting looks reasonable.

Increase Daemon memory only after evidence of GC/heap pressure, and re-evaluate worker/fork counts simultaneously because memory and concurrency are coupled capacity choices.

8. Local profile versus hosted/commercial telemetry

Evidence path Mandatory? Strength Governance consideration
--profile local HTML Yes for this chapter. Free, no account, phase/task timing sufficient for many first diagnoses. Artifact may contain project/task names; still handle as build evidence.
Build Scan via --scan No. Richer timeline, dependencies/environment/context and shareable record. Publishes build metadata externally; review privacy/data policy first.
Gradle Profiler / JVM profiler Optional. Repeated scenarios, method-level CPU/allocation insight for custom plugins/tasks. Extra tooling and interpretation cost; profile only after high-level evidence narrows the target.

Hosted telemetry can be valuable, but the mandatory course remains free-compatible. A team should not need a paid service to prove that a configuration script spends 800 ms scanning files or that three project tasks are serialized.

9. Know which control belongs to which system

Question Owner / layer Not solved by
Which JDK runs Gradle? Wrapper/Gradle runtime/Daemon JVM policy. Java --release alone.
Which JDK compiles/tests app code? Java/Kotlin toolchains and task launchers. org.gradle.jvmargs.
How many CI jobs run simultaneously? CI scheduler/agent pool/container limits. --max-workers inside one build.
Where dependencies come from? Repository policy/resolution from Chapters 19–20. File-system watching.
Whether output reuse is correct/trusted? Task model/build cache/configuration cache from Chapter 24. Daemon reuse.
Whether developer file edits are noticed efficiently? Gradle VFS/file watcher + filesystem support. Project parallelism.

10. Decision table: three environments

Environment Starting policy Why Evidence before changing
Developer workstation, 8 cores/16 GiB, local SSD Daemon on; default VFS; start with default workers; parallel only if multi-project profile supports it. Warm edit loops benefit from reuse; enough headroom but graph still determines scaling. Three representative edits, profile, CPU/RAM, task outcomes.
Ephemeral CI, 2 vCPU/4 GiB Daemon allowed within job; isolate GRADLE_USER_HOME; try workers 1–2; parallel only after same-workload comparison. CPU/memory are constrained; oversubscription is easy. Agent quota, warm dependency state, profile, tests/artifact identity.
Persistent self-hosted CI, 16 cores/64 GiB, many concurrent jobs Daemon reuse with standardized JVM args; worker ceiling per job below host max; monitor aggregate host pressure. One build can starve neighboring jobs if each assumes all 16 cores. Per-job and fleet throughput, queue latency, memory/GC, failure rate.

11. Worked choice

A four-module build on a 2-vCPU runner has --parallel --max-workers=8, maxParallelForks=4 for each test task, and a 2 GiB Gradle heap. Profiles show compilation is fast but tests slow down as forks increase; the runner sometimes swaps.

The defensible correction is not “more heap and 16 workers.” Start by lowering test forks and Gradle worker capacity to fit the two CPUs and memory limit, then measure. Preserve tests, task dependencies, and artifact checksums. If wall-clock improves and swap/GC falls, the evidence supports a smaller concurrency budget.

Knowledge check

Why can enabling the Daemon help even in CI?

Why is --max-workers not a host-wide CPU scheduler?

When does --parallel have little effect?

Why might process-isolated Worker API work reduce throughput?

What should a team do before publishing a Build Scan from sensitive CI?

Which setting controls test JVM memory?

Official references and version notes

Version-sensitive behavior was rechecked against current Gradle primary documentation on 2026-08-24. The mandatory path uses Gradle 9.7.1 through the previously verified Wrapper, JDK 21 as the Gradle runtime/toolchain, Java 17 as the project target, JUnit 6.1.3 only for the small local test fixture, and an isolated GRADLE_USER_HOME. Build Scan publication and commercial/hosted telemetry are optional; the required evidence uses the free local --profile report and ordinary logs.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.