A memory leak by definition is an increase of memory usage that never returns to a lower baseline level. Can you see the problem in this heap graph below? After each garbage collection, the heap usage drops a little, but never quite returns to the same baseline. This program has a slow but steady memory leak.
This is a screenshot of VisualVM - my favorite tool for analyzing and debugging Java memory leak problems. It comes pre-installed with the Java JDK 6+. VisualVM can do a lot of things, but I will be focusing on its memory profiling features and how to use them to fix memory leaks.
Sometimes finding the cause of a memory leak can be tedious and fraught false positives. It is common to discover a potential leak but later realize that memory is being managed by a cache and is actually working as designed. 1 Finding a memory leak takes a lot of diligence and I recommend a strict process to avoid driving yourself crazy.
The general procedure for finding and fixing memory leaks is as following:
Identify The Leak Action: The goal here is to find the user interaction with your app that causes this leak pattern. Play around with your app. Hit buttons, open and close windows, etc. After each interaction, hit the "Perform GC" button and note the heap usage numbers. If the usage never returns to some baseline level, that might be a leak1
Snapshot: Now that you have identified the action that causes your leak, its time to figure out what code is responsible. The idea here is to capture a heap dump before and after performing the Leak Action and then comparing the two dumps. Hit the Heap Dump button, then perform the Leak Action, then hit the Heap Dump button again.
Compare Shapshot Diffs: A super powerful feature of VisualVM is it allows you to compare two heap snapshots and displays the diffs between them. This will give you counts of new objects created between snapshots.
Path To GC Root Once you have identified some suspicious objects you can use VisualVM to find the paths to the GC roots. Right-click the object in the objects view and select "Path to nearest GC root." A tree-view will display all of the references that other objects are holding to the object in question. By examining these paths closely you will hopefully discover a path that should really not exist. Now you know what is causing your memory leak.
Although a cache growing too large borderlines with the definition of a memory leak. Its really all about getting to know your app intimately. ↩