Java Debugging Part 4: Memory Leaks

Memory leaks are something particulary hard to debug. You know when they occured, because your program crashed with the uncaught excpetion OutOfMemoryError, but finding out what caused it can be a nightmare without the proper tools.

Automatic Heap Dumps

By adding -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-or-dir-path> to your java virtual machine arguments, the JVM will automatically create a dump of the entire heap, if it runs out of memory.

In gradle for example your add them like this:

application {
    mainClassName = "package.ClassWithMainMethod"
    applicationDefaultJvmArgs = listOf("-XX:+HeapDumpOnOutOfMemoryError",
    "-XX:HeapDumpPath=dump.hprof")
}

This will create dump the heap to "dump.hprof" wherever the JVM happens to be running. You should enter some absolute path here, to easily find your dump.

Strategy

Whenever you run out of memory, it is most likely because one or more of your classes has some reference stored somewhere and thus is not getting garbage collected. More and more instances are created until the memory is used up.

This means that the first thing we look for are our classes with too many instances. To do that, load the dump into the "Eclipse Memory Analyser" and select the "Histogram" view.

Sort by Objects descending and look for the first class you recognize. Sorting by instance number is the fastest way of identifying the class(es) that are the problem.

The other measures are mostly kind of useless, due to how most java programs are structured. If you sort by actual heap usage, you will find that Strings and byte arrays take up the most memory. Who would have thought...

Fixing the problem

First of all some more general things about memory leaks. To understand a memory leak in java, you have to understand how the garbage collector works.
In java you don't allocate memory by hand, it all happens behind the scenes, but it still does happen.

The first Objects you create in your static void main(String[] args) method are acting as so called root nodes. Anything linked to these nodes (directly or indirectly) will stay allocated. When the garbage collector runs, it checks for instances that don't have a connection (direct or indirectly), to these root nodes anymore and unallocates their used memory.

This means you now have to go over your code with a fine toothed comb and look for any spots where these references could jam.

If you have a complex class hierachry it helps visualizing it using something like draw.io.

Series Overview

©
Tobias Hübner