Backend Development 10 min read

Diagnosing Java Memory Leaks: JVM GC Roots, Monitoring, and Code Fixes

This article explains how Java memory leaks can occur despite automatic garbage collection, describes JVM GC‑Root analysis, outlines practical monitoring with Spring Boot Actuator, Prometheus, and Grafana, and provides step‑by‑step debugging commands and code adjustments to locate and fix the leak.

360 Tech Engineering
360 Tech Engineering
360 Tech Engineering
Diagnosing Java Memory Leaks: JVM GC Roots, Monitoring, and Code Fixes

Memory leak (Memory Leak) refers to heap memory that has been dynamically allocated but not released, causing waste and performance degradation.

Although Java uses automatic garbage collection (GC), leaks can still happen when objects remain reachable from GC roots.

JVM determines unreachable objects via reachability analysis starting from GC Roots such as stack variables, static fields, JNI references, etc.

When an object has no reference chain to any GC Root, it is considered garbage and will be reclaimed.

In a real incident, a service showed frequent timeouts, high CPU load, and required restarts; analysis revealed a memory leak caused by a ListAppender accumulating logging events.

Monitoring was set up using Spring Boot Actuator, Prometheus, and Grafana to collect JVM metrics without impacting the application.

Key configuration snippets:

compile 'org.springframework.boot:spring-boot-starter-actuator'
compile 'io.micrometer:micrometer-registry-prometheus'
management:
  endpoints:
    web:
      exposure:
        include: health,prometheus

To locate the leak, the process ID is obtained, a heap dump is generated with jmap -dump:live,format=b,file=dump.hprof <pid> , and analyzed using Eclipse MAT.

Analysis showed a chain from ch.qos.logback.core.spi.AppenderAttachableImpl to ch.qos.logback.classic.Logger and ultimately to a static StaticLoggerBinder singleton, whose ListAppender kept growing.

Fix: ensure the ListAppender is detached in a finally block:

// construct logger
Logger logger = org.slf4j.LoggerFactory.getLogger(cronBean.getClass());
// temporary ListAppender
ListAppender
listAppender = new ListAppender<>();
((ch.qos.logback.classic.Logger)logger).addAppender(listAppender);
listAppender.start();
try {
    // business logic
} finally {
    ((ch.qos.logback.classic.Logger) logger).detachAppender(listAppender);
    MDC.remove("tid");
}

Key takeaways: understand GC fundamentals, use monitoring tools (Grafana, Prometheus, MAT, Arthas) to diagnose, and avoid code patterns that retain references.

References: How Garbage Collection Works

JavaJVMmonitoringgarbage collectionMemory LeakSpring Boot
360 Tech Engineering
Written by

360 Tech Engineering

Official tech channel of 360, building the most professional technology aggregation platform for the brand.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.