Card 07/ 09

GotchaDifficulty: Advanced2 min

Memory Leaks in Java: References You No Longer Need

Eleven gigabytes after nine days, fixed by a restart for another nine.

A memory leak in Java is an object your code no longer needs that is still reachable (still connected, through some chain of references, to a variable the program is using), usually because a long-lived collection still holds it. The collector is working correctly; it cannot remove what something still refers to.

A service that has run for nine days is using eleven gigabytes. Restarting it fixes the problem for another nine days. Nothing in the code allocates anything unusual:

java
class SessionRegistry {
    private static final Map<String, Session> active = new HashMap<>();

    static void login(String id, Session s) { active.put(id, s); }
}

There is no logout. Every session ever created is still in that map, the map is a static field, and a static field is a garbage collection root, one of the starting points the collector follows to decide what is still in use. Every one of those sessions is reachable, so every one of them is alive.

This is the same rule as a variable reassigned so that nothing points at its old object any more, in a setting that looks nothing like it: the collector has not changed its mind about anything, and the only question is still whether a path from a root reaches the object. Here it does, nine days of them.

The places this shape turns up
HolderWhat it accumulates
A static collection with no removalEverything ever added
A listener registered and never removedThe listener and everything it points at
A cache with no size limit or expiryEvery key ever requested
A ThreadLocal (a value kept separately for each thread, each path of execution) in a pooled threadOne object per thread, for the life of the pool

Give every long-lived collection a removal path before it has anything in it: a logout, an eviction policy, a bounded cache. A map that only ever grows is a leak that has not had time yet.