Understanding the Gradle Daemon
What Is the Gradle Daemon?
The Gradle Daemon is a background process that runs build logic to improve build performance. It avoids JVM startup overhead by persisting between builds. However, the persistent nature of the daemon can lead to memory retention and leaks, especially when large build graphs, annotation processors, or misconfigured plugins are involved.
When Daemon Leaks Become a Problem
Memory leaks typically surface in the following scenarios:
- Very large multi-module projects (50+ modules).
- Frequent use of kapt (Kotlin annotation processor).
- Plugin misuse that retains references to build scopes.
- Custom tasks or workers holding static references.
Symptoms and Early Warning Signs
Key Indicators of Memory Leaks
- Gradle builds get progressively slower.
- JVM crashes with
java.lang.OutOfMemoryError: Java heap space
. - CI pipelines randomly fail on tasks like
:compileJava
or:kaptGenerateStubs
. - Memory usage of the Gradle daemon exceeds allocated Xmx.
Initial Diagnostics
jps -l | grep GradleDaemon jmap -heap <pid> jstat -gc <pid> 1000 10
Observe heap and GC patterns. High tenured generation retention or lack of GC effectiveness indicates leaks.
Common Root Causes
Annotation Processors
Annotation processors like Dagger or Room can leak memory due to large symbol graphs or classpath scanning. Improperly scoped annotation processors may retain class references even after tasks complete.
Gradle Plugin Issues
Third-party plugins sometimes retain project references or task graphs in static fields. These references prevent garbage collection across build cycles.
Custom Build Logic
Groovy or Kotlin DSL tasks may inadvertently capture build context:
ext.leakyList = [] tasks.register("leakyTask") { doLast { leakyList.add(project) } }
This retains the project
object in memory, which can be substantial in large builds.
Step-by-Step Troubleshooting
1. Enable Detailed GC Logging
org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gradle-gc.log
This helps track how memory is being allocated and if GC is failing to clean up memory effectively.
2. Capture and Analyze Heap Dumps
When an OOM occurs, analyze the heap dump using tools like Eclipse MAT or VisualVM:
jmap -dump:format=b,file=heap.hprof <pid>
Look for large retained sets, especially those referencing Project, Task, or ClassLoader objects.
3. Identify Problematic Plugins or Tasks
Temporarily disable non-core plugins and observe memory behavior. Isolate plugins using the --no-daemon
flag to force daemon restart per build:
./gradlew build --no-daemon
This can pinpoint if leaks disappear with daemon isolation.
4. Limit Daemon Lifetime in CI
org.gradle.daemon=false
In CI, use ephemeral daemons to avoid memory build-up between jobs. Alternately, restart the daemon after every N builds using job wrappers.
5. Use Gradle Profiler
Gradle Profiler can be used to simulate builds and track memory metrics over multiple iterations.
Best Practices for Long-Term Stability
Gradle Settings
- Set reasonable
org.gradle.jvmargs
ingradle.properties
. - Cap daemon memory with
-Xmx
and set-XX:+UseG1GC
. - Use
--no-daemon
selectively in CI where deterministic builds are key.
Code and Plugin Hygiene
- Audit custom tasks to avoid static references or long-lived closures.
- Ensure annotation processors are scoped and updated regularly.
- Avoid capturing the project or task object in global extensions.
Conclusion
Memory leaks in the Gradle daemon can undermine performance and reliability across enterprise-grade builds, especially as modular complexity scales. A thorough understanding of memory behavior, combined with structured diagnostics and preventive configuration, can mitigate these risks. Treat the daemon as a long-running service—subject to all the challenges of memory management in persistent processes—and design build logic accordingly.
FAQs
1. Is disabling the daemon a good long-term solution?
Not necessarily. It solves memory issues at the cost of slower builds. Prefer leak elimination unless CI time is expendable.
2. Can Kotlin DSL lead to more leaks than Groovy?
It can if closures are misused. Both DSLs support lazy configuration, but careless use of lambdas can capture unintended contexts.
3. How can I monitor daemon memory usage in real time?
Use tools like jstat
, jconsole
, or VisualVM
to attach to the Gradle daemon process and observe heap metrics.
4. Are there tools to automatically detect plugin-related leaks?
Heap analyzers like Eclipse MAT can trace retained objects to plugin classes. Some Gradle profiling tools also track classloader growth.
5. Should I limit the number of concurrent Gradle daemons?
Yes, especially on shared CI workers. Use org.gradle.workers.max
to tune parallelism and reduce memory contention.