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 in gradle.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.