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.
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,prometheusTo 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
360 Tech Engineering
Official tech channel of 360, building the most professional technology aggregation platform for the brand.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.