Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java garbage collection: diagnose retention before tuning

Last updated: 28 Sept 20263 min read
tutorial
IntermediateBy AITrove Editorial

Garbage collection recovers storage from objects the runtime can no longer reach; it does not close application resources or guarantee a fixed pause time.

Java 21+. Use a JDK that supports this release.

Compare the same workload phases

Capture a baseline after startup, run a bounded workload, allow it to reach its usual idle state, then compare the retained trend. One high heap reading during an allocation burst is different evidence from a rising floor after repeated equivalent batches.

The program measures currently used heap through MemoryMXBean before and after allocating a small retained buffer. The numbers vary across runs and are not a deterministic leak test. Collection, heap resizing, and other runtime work can alter them.

A histogram identifies common classes; it does not prove why they remain reachable. A heap dump and ownership analysis can show the retaining path. Dumps may contain application data and need an intentional storage and access policy.

Choose a diagnostic before a collector flag

For a Java 21 run, unified GC logging can show collection events and pause information. A bounded log file keeps a long-running diagnostic from consuming an unbounded disk budget. Compare latency and throughput under the same input rather than assuming fewer collections means a faster service.

System.gc is a request, not a contractual test barrier. Do not insert it between production requests to compensate for an unbounded cache. Reduce the retaining owner or give it an explicit capacity first.

A collector cannot reclaim an object still reachable through a live owner. Reachability explains that requirement. Different collectors trade pause behavior, concurrent work, and memory headroom; no one setting removes those tradeoffs.

Working program

Java
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;

public class HeapObservation {
    private static byte[] retained;
    public static void main(String[] args) {
        MemoryMXBean memory = ManagementFactory.getMemoryMXBean();
        long before = memory.getHeapMemoryUsage().getUsed();
        retained = new byte[256 * 1024];
        long after = memory.getHeapMemoryUsage().getUsed();
        System.out.println("beforeBytes=" + before);
        System.out.println("afterBytes=" + after);
        System.out.println("retainedBytes=" + retained.length);
        retained = null;
    }
}

Cost and design choices

The buffer uses O(n) payload storage for n bytes. Clearing this one retaining reference permits later reclamation if no other owner exists; it does not synchronously lower the heap measurement.

Diagnostic capture adds work. A live heap dump or profiling session can perturb latency and may require a maintenance plan. Record the tool settings alongside the measured result.

Common Mistakes

  • Do not label every temporary peak a memory leak.
  • Do not use forced collection as proof of production behavior.
  • Do not tune a collector before checking an unbounded retaining structure.

Connect the contracts

Compare the boundary explained in Enclosing references with the assumptions made by this program.

java
gc-diagnostics
Storage details