Leaks in a collected language
A collector cannot free what your code still points at. That is every Java memory leak, in one line.
Open this lesson in the learning hubKey points
- A Java leak is not lost memory. It is memory you are still holding a reference to, by accident.
- The usual culprit is a
staticcollection that only ever grows. Static means root, and root means immortal. - Unbounded caches do the same thing. Bound them by size or by age, or use a cache library that already does.
- Listeners and callbacks that get registered and never removed drag whole object graphs along with them.
- A
ThreadLocalon a pooled thread outlives the request that set it. Clear it in afinallyblock, every time. - The signature is old gen creeping upwards after every full GC. Take two heap dumps an hour apart and compare what grew.
Example
import java.util.ArrayList;
import java.util.List;
public class Main {
static final List<byte[]> CACHE = new ArrayList<>(); // static, never evicted: the classic leak
public static void main(String[] args) {
System.out.println("used at start : " + usedMb() + " MB");
for (int i = 0; i < 200; i++) { CACHE.add(new byte[100 * 1024]); } // ~20 MB
System.gc();
System.out.println("after 200 puts : " + usedMb() + " MB <- GC cannot touch it");
CACHE.clear();
System.gc();
System.out.println("after clear() : " + usedMb() + " MB <- now unreachable, freed");
System.out.println();
System.out.println("Nothing was lost. The code simply kept pointing at it.");
}
static long usedMb() {
Runtime rt = Runtime.getRuntime();
return (rt.totalMemory() - rt.freeMemory()) >> 20;
}
}
The collector is not leaking. Your reference is.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JVM course, and every lesson in it is listed on the JVM contents page.