Quick answer
A Java memory leak is unwanted object retention that continues to grow for the relevant workload lifetime. Test that hypothesis with trends first: allocation, after-collection live set, class counts, cache size, request volume, and service version. Use a class histogram to narrow object populations before collecting a heap dump. Analyze GC roots, dominators, retained size, ownership, and object paths; object count alone does not identify the leak.
Heap inspection can be high impact. A Java 25 GC.class_histogram may pause according to heap size and content. GC.heap_dump is documented as high impact, can request a full collection, needs substantial disk, and can capture credentials, personal data, payloads, and business objects. Require an approved location, access policy, encryption, retention, deletion, and rollback before capture.
Learning objectives
- Distinguish allocation pressure, a large legitimate live set, heap undersizing, and unwanted retention using comparable evidence.
- Move from class histogram to heap graph analysis without exposing data or treating one large object population as root-cause proof.
Prerequisites
Read JVM GC Logs and Pause Diagnosis and understand why after-collection occupancy under comparable load is more useful than one arbitrary heap percentage.
Evidence and system boundary
A histogram aggregates objects by class and shallow bytes. A heap dump preserves an HPROF snapshot of managed objects and references. Neither artifact explains process-native memory, kernel page cache, container OOM policy, database state, or the user requests that were not captured. Heap contents can be security-sensitive even when logs are redacted.
These Java 25 commands are explanatory examples, not approved production actions:
jcmd <pid> GC.class_histogram
jcmd <pid> GC.heap_dump filename=/approved/secure/order-service.hprof
The exact jcmd help output states impact for each command. Run jcmd <pid> help GC.heap_dump on the selected runtime before deciding. Confirm free space, expected dump size, pause tolerance, filesystem permissions, encryption, transfer path, retention owner, and secure deletion.
Build a retention hypothesis
Compare the same workload stage across time. A warm cache grows and then stabilizes; a leak continues retaining data beyond its contract. Allocation can be high while live set remains flat. A larger live set after a feature release may be legitimate if the feature intentionally keeps more data and the capacity budget changed with it.
Start with class histograms across controlled intervals. Look for classes whose counts and bytes rise with no matching bounded state. Class names can still mislead: a byte[] may belong to HTTP buffers, caches, compressed payloads, images, or framework internals. Ownership requires the reference graph.
In a heap analyzer, use dominator relationships and retained size to find objects whose removal would make a large subgraph unreachable. Follow paths to GC roots and inspect application ownership. Common patterns include unbounded maps, listeners never removed, thread-local values held by long-lived workers, classloaders retained across redeployments, queues without admission, and caches whose expiry cannot keep up.
Compare more than one artifact only when capture policy permits it. A single dump cannot prove growth. Dumps from different loads or lifecycle stages create false comparisons. Preserve traffic, uptime, version, heap policy, and capture time with each artifact.
Production failure scenario
The Spring order service heap floor rises after each daily catalog refresh. Histograms show growing byte[] and cache-entry counts. A bounded dump from a canary reveals an old classloader retained through a scheduled listener, which holds the previous cache graph.
The team removes the listener during lifecycle shutdown and repeats the same refresh workload in a canary. Classloader count and after-collection live set stabilize. It then verifies checkout latency, RSS, restart behavior, and durable orders. The heap graph supported the ownership hypothesis; a repeated workload supported the fix.
Common misconceptions
- High allocation is not a leak when objects die within the intended lifecycle.
- Largest shallow size is not necessarily largest retained ownership.
- A
byte[]class histogram does not identify the subsystem that owns the bytes. - Forcing
System.gc()is not a leak test or a safe recovery plan. - A successful heap dump does not mean the capture was safe, complete, or representative.
Decision checklist
- Establish comparable live-set and class-count trends before capturing a dump.
- Start with the lowest-impact evidence that can separate hypotheses.
- Read the exact Java 25 command impact and available disk before capture.
- Treat heap artifacts as potentially sensitive production data.
- Record version, uptime, traffic, heap policy, and capture time.
- Analyze retained ownership and GC-root paths, not class size alone.
- Reproduce the retention lifecycle in a canary or controlled environment.
- Verify process memory, user outcomes, and durable state after remediation.
Related reading
Continue with JVM Native Memory, Metaspace, and Container OOM Diagnosis when process memory exceeds the managed heap. Follow the Java Concurrency course and Java Concurrency topic cluster for the ordered diagnostic path.
Official sources
- Java 25 jcmd specification
- Java 25 diagnostic tools
- Java 25 troubleshooting guide
- Eclipse Memory Analyzer documentation
Official sources accessed August 19, 2026. Recheck command impact, analyzer version, artifact policy, and the selected JVM before collecting or transferring heap data.