skip to content

Test JVM Forking & Args

Controlling the forked test JVM: heap sizes, JVM arguments, and which toolchain launcher runs it. Asked because tests that pass locally and run out of memory in CI usually differ in exactly these settings.

on this pageshow

questions

5

How do you control the heap size of the JVM that runs your tests in Gradle, and where do you configure it?

level: juniorimportance: must knowfreq 55%

answer

  1. tests fork a separate JVM
  2. minHeapSize=-Xms, maxHeapSize=-Xmx
  3. org.gradle.jvmargs sizes the DAEMON not tests
  4. each parallel fork gets its own heap
  5. string values like 512m / 2g

basics

~10 s

Tests run in a separate forked JVM. On the test task set minHeapSize and maxHeapSize (e.g. "256m" / "1g"). These map to the forked JVM's -Xms and -Xmx, not the Gradle daemon's heap.

solid answer

~40 s

Gradle's `Test` task forks one or more separate JVMs to actually run tests, isolated from the Gradle daemon. You size that forked JVM with `minHeapSize` and `maxHeapSize` on the `test` task, which become `-Xms` and `-Xmx` for the forked process. A common mistake is putting `-Xmx` in `org.gradle.jvmargs` (gradle.properties) expecting it to affect tests — that only sizes the daemon. For one-off control you can also pass `jvmArgs("-Xmx2g")`, but `maxHeapSize` is the idiomatic, readable property. Values are strings like "512m" or "2g". When tests run out of memory you raise `maxHeapSize`; raising the daemon heap has no effect on the forked test JVM.

code

kotlin · 6 lines
kotlin
tasks.test {
    minHeapSize = "256m"
    maxHeapSize = "2g"
    // equivalent low-level form:
    // jvmArgs("-Xms256m", "-Xmx2g")
}

go deeper

for a junior

Know tests fork a separate JVM and that minHeapSize/maxHeapSize on the test task set -Xms/-Xmx.

for a middle

Distinguish daemon heap (gradle.properties) from forked test heap; size per-worker memory with parallel forks in mind.

for a senior

Reason about CI RAM budgeting: total ≈ forks × maxHeapSize; pick values that avoid both OOM and host thrashing.

for a principal

Set org-wide defaults via a convention plugin so teams don't rediscover the daemon-vs-test-heap trap; standardize CI memory budgets.

## Why tests run in a separate JVM Gradle does not run your tests inside the long-lived Gradle daemon. The `Test` task **forks** one or more fresh JVMs (the *test workers*) and executes test classes there. This isolates test code — its static state, classloaders, custom JVM flags, and crashes — from the daemon that orchestrates the build. Because it is a distinct process, it has its **own** heap, its own system properties, and its own JVM arguments, all configured on the `test` task rather than inherited from the daemon. ## Heap properties The `Test` task (which extends `ProcessForkOptions`/`JavaForkOptions`) exposes: - `minHeapSize` → the forked JVM's `-Xms` (initial heap). - `maxHeapSize` → the forked JVM's `-Xmx` (maximum heap). Both are `String` properties taking values like `"256m"`, `"1g"`. Setting `maxHeapSize = "2g"` is exactly equivalent to adding `jvmArgs("-Xmx2g")`, but it is the readable, intention-revealing form. ## The classic confusion: daemon heap vs test heap `org.gradle.jvmargs=-Xmx4g` in `gradle.properties` sizes the **Gradle daemon**, the process that configures and runs the build graph. It does **not** size the forked test JVM. If your tests throw `OutOfMemoryError`, raising the daemon heap changes nothing — you must raise `maxHeapSize` on the `test` task. Conversely, a heavy test suite does not need a giant daemon heap. ## Where it applies With `maxParallelForks > 1` Gradle launches several test workers; **each** worker gets the heap you configured, so total memory ≈ `maxParallelForks × maxHeapSize`. Keep that in mind on CI agents with limited RAM. ```kotlin tasks.test { minHeapSize = "256m" maxHeapSize = "2g" } ```

  • Tests OOM but you set -Xmx4g in gradle.properties. Why didn't it help?
    `org.gradle.jvmargs` in gradle.properties only sizes the Gradle daemon. The forked test JVM ignores it; you must set `maxHeapSize` (or jvmArgs -Xmx) on the `test` task.
  • With maxParallelForks=4 and maxHeapSize=2g, what's the worst-case test memory footprint?
    Up to ~8g — each of the 4 workers gets its own 2g max heap simultaneously.

The daemon is the kitchen manager; each test worker is a separate cook with their own counter space. Giving the manager a bigger desk doesn't give the cooks more room — you size the workers directly.

saying these in an interview costs you the question

  • Claiming tests run inside the Gradle daemon / share its heap.
  • Believing org.gradle.jvmargs affects the test JVM's heap.
  • Forgetting that parallel forks multiply total memory use.

context

open as a page

How do you make your tests run on a specific JDK version (e.g. JDK 21) independent of the JDK Gradle itself runs on?

level: middleimportance: must knowfreq 45%

basics

~10 s

Use Gradle's Java toolchains. Set javaLauncher on the test task from javaToolchains.launcherFor { languageVersion = JavaLanguageVersion.of(21) }, or set a project-wide java.toolchain. Gradle locates/provisions that JDK and forks the test JVM with it.

open as a page

How do you pass custom JVM and -XX flags to the test JVM in Gradle, and what's the difference between assigning and appending them?

level: middleimportance: must knowfreq 50%

basics

~10 s

On the test task use jvmArgs(...) to add flags like -XX:+HeapDumpOnOutOfMemoryError. Calling jvmArgs("-X...") appends; assigning jvmArgs = listOf(...) replaces the whole list. Heap shortcuts use minHeapSize/maxHeapSize.

open as a page

Tests in a forked JVM are slow to start and you suspect JVM startup/JIT overhead. What test-JVM forking and arg knobs affect this, and what are the trade-offs?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Each test worker is a forked JVM with startup + JIT cost. forkEvery controls how many test classes a worker runs before being restarted (0 = never restart, fastest; >0 = more isolation, more restarts). Right-sizing heap and adding tiered-compilation flags via jvmArgs can cut warmup.

open as a page

When testing a JPMS modular project, how do you pass module-path / module-system arguments (like --add-opens or --add-modules) to the test JVM in Gradle?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Add the module flags to the test task's jvmArgs, e.g. jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") or --add-modules. For real JPMS modular runs Gradle can also build a module path automatically when a module-info.java is present.

open as a page