skip to content

A large multi-module build fails with 'java.lang.OutOfMemoryError: Java heap space' during configuration/compilation. How do you diagnose and fix it via the daemon JVM args?

level: middleimportance: must knowfreq 55%

answer

  1. localize: daemon vs forked worker
  2. raise -Xmx in org.gradle.jvmargs
  3. +HeapDumpOnOutOfMemoryError + HeapDumpPath
  4. --stop so new args apply
  5. don't exceed RAM / starve workers

basics

~10 s

Confirm the OOM is in the daemon (not a forked test worker), then raise the daemon heap in gradle.properties via org.gradle.jvmargs=-Xmx (e.g. -Xmx3g) and add -XX:+HeapDumpOnOutOfMemoryError to capture a dump if it recurs.

solid answer

~40 s

First identify **which JVM** ran out of memory. 'Java heap space' during configuration, plugin work, or in-process compilation points at the **daemon** itself; if the stack trace is inside a test worker it's the `Test` task's heap, not `org.gradle.jvmargs`. For a true daemon heap OOM, increase `-Xmx` in `org.gradle.jvmargs` (e.g. from 1g to 3g) in the committed `gradle.properties`, and add `-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=build/heapdumps` so the next failure leaves an `.hprof` you can open in a profiler. Because the daemon is long-lived, a stopped daemon must restart to pick up new args — run `--stop` or just start a fresh build. Don't over-allocate blindly: an `-Xmx` larger than free RAM causes swapping and slower builds, and on CI competes with forked workers. Pair sizing with `--max-workers` awareness so total memory (daemon + workers) fits the machine.

code

toml · 3 lines
toml
# gradle.properties
org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m \
  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=build/heapdumps

go deeper

for a junior

Know to raise -Xmx in gradle.properties for a daemon heap OOM.

for a middle

Distinguish daemon vs forked-worker OOM, add heap-dump diagnostics, and restart the daemon.

for a senior

Reason about total memory budget (daemon + parallel workers), CI container limits, and using heap dumps to find leaks rather than inflating heap.

for a principal

Define memory-sizing policy across repos/CI agents and a workflow for triaging build OOMs (dump capture, retention analysis, sizing relative to agent RAM).

## Step 1 — localize the OOM `OutOfMemoryError: Java heap space` can come from two very different places: - **The daemon** — during project configuration, applying plugins, dependency resolution, or in-process `JavaCompile`/Kotlin compilation. Fixing this is the job of `org.gradle.jvmargs`. - **A forked worker** — a `Test` worker, a `JavaExec`, or a forked compiler. The stack trace runs through the worker; the fix is that task's `maxHeapSize`, **not** `org.gradle.jvmargs`. Read the stack trace and the failing task to decide. A quick tell: configuration-time or `:compileJava`/`:compileKotlin` OOM is usually the daemon. ## Step 2 — raise the daemon heap In the committed `gradle.properties`: ```properties org.gradle.jvmargs=-Xmx3g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=build/heapdumps ``` `-Xmx` caps the heap. Pick a value that fits the build's working set but leaves headroom for the OS and for forked workers running in parallel. `-XX:+HeapDumpOnOutOfMemoryError` plus `-XX:HeapDumpPath` makes the next OOM dump the heap to a file you can analyze in a tool like Eclipse MAT or VisualVM to find the real retention (e.g. a plugin holding everything in memory). ## Step 3 — make the daemon pick up the change The daemon is reused across builds. A running daemon keeps its **old** JVM args; the new args only apply to a daemon started with them. After editing `org.gradle.jvmargs`, the safest move is `./gradlew --stop` (or let Gradle spawn a fresh daemon — changed JVM args make the old daemon incompatible). Then the next build runs with the new heap. ## Step 4 — don't over-allocate Bigger is not free: - An `-Xmx` larger than available RAM forces the OS to swap, which makes builds dramatically slower than the OOM would have. - On a parallel build, total footprint = daemon heap + (number of forked workers x worker heap). On a CI box with limited RAM, an aggressive daemon `-Xmx` can starve the test workers or get the daemon OOM-killed by the container. So size empirically: reproduce, capture a dump or watch GC, raise to comfortably above the observed peak, and verify it fits alongside `--max-workers` and test-worker heaps. ## Why GC/heap-dump matters If raising `-Xmx` only delays the OOM, you likely have a leak (e.g. an accumulating cache, or a plugin holding configuration objects). The heap dump tells you what's retained so you fix the root cause instead of endlessly inflating the heap.

  • You doubled -Xmx and the OOM still happens, just later. What does that suggest and what do you do?
    It suggests a memory leak (something accumulating), not merely an under-sized heap. Capture the heap dump via -XX:+HeapDumpOnOutOfMemoryError and analyze retention (e.g. in Eclipse MAT) to find and fix the root cause.
  • After editing org.gradle.jvmargs the build still OOMs at the same heap. Why might the change not have applied?
    A still-running daemon kept its old args. Run ./gradlew --stop (or otherwise force a fresh daemon) so a new daemon starts with the new -Xmx.

saying these in an interview costs you the question

  • Reflexively raising -Xmx for a test-worker OOM that needs the Test task's maxHeapSize.
  • Setting -Xmx above physical RAM — causes swapping or container OOM-kill, slower than before.
  • Assuming a running daemon adopts new jvmargs without a restart.

context