· 3 min read Posted by Tadeas Kriz
Overriding Gradle Daemon jvmargs
The Problem
Many of us need to give Gradle more RAM to work with,
especially when working with Kotlin Multiplatform.
So we open gradle.properties
in the project,
and add or change the property org.gradle.jvmargs
.
We slap -Xmx6g -XX:+UseParallelGC
(or similar) in and call it a day.
It’s only until later we may find weird behavior of our Gradle Daemons,
like seeing a “daemon disappeared” message in GitHub Actions.
Another side-effect is that -XX:+HeapDumpOnOutOfMemoryError
is no longer passed to JVM,
making debugging of one-off out of memory errors difficult.
The Cause
To understand why this happens,
we need to look at how Gradle usually starts daemons.
When a JVM is started,
it has default values for JVM args.
It’s common that those defaults are replaced with a more domain-specific values.
Without the org.gradle.jvmargs
,
Gradle has its own domain-specific values it passes to the JVM.
However,
when we provide org.gradle.jvmargs
,
Gradle no longer provides its defaults,
but only whatever we gave it in org.gradle.jvmargs
.
The Solution
So what we should do instead,
is take the args Gradle would normally use
and expand them.
Considering that Kotlin requires JDK 8 to work,
we can safely copy the arguments from DEFAULT_JVM_8_ARGS
.
So before we start changing the arguments,
it should look something like this:
org.gradle.jvmargs=-Xmx512m -Xms256m -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError
Note that these values are from Gradle 8.7 and might differ between releases.
After increasing the memory limit and enabling parallel GC, it’d look something like this:
org.gradle.jvmargs=-Xmx6g -Xms256m -XX:MaxMetaspaceSize=384m -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC
Notice the -XX:MaxMetaspaceSize
argument.
If your builds are complex,
you might see Gradle daemon running out of metaspace.
It will inform you in the build log,
so you’ll know to increase the value.
Always make sure that argument is present though,
as JDK default is unlimited.
That can result in Gradle daemon using more and more native memory,
eventually running out and crashing.
Conclusion
This issue is way too common and often doesn’t get noticed. It can lay dormant in your project for a long time, until you suddenly start seeing weird behavior. And since it’s this common, it made it’s way to many Open Source projects.
To name a few:
- https://github.com/touchlab/KaMPKit
- https://github.com/joreilly/Confetti
- https://github.com/JetBrains/compose-multiplatform-core
And unfortunately even the official Kotlin Multiplatform Wizard and the templates in its template gallery:
A shoutout to Android documentation, which mentions this issue explicitly in Optimize your build. Gradle documentation mentions the defaults, but doesn’t mention the issue with overriding them.
Let’s hope Gradle finds a way to improve this in the future.
Until then,
we need to be careful when setting org.gradle.jvmargs
.