Spot and Find Memory Leaks with JProfiler

JProfiler is a java profiler developed by ej-technologies that helps developers resolve performance bottlenecks, find memory leaks and understand threading issues. It provides many useful tools such as memory profiling, a way to analyze heap snapshots and a live memory view that shows all objects that are currently in use. JProfiler brings many more features, like database profiling but these are out of the scope of this blog post.

There are many blog posts and articles about different kind of memory leaks and how their memory footprint looks like, unfortunately, it is very hard to find the memory leak based only on the memory graph. We will take a look at a memory leak and investigate how it would be possible to detect using the tools that JProfiler offers.

JProfiler displays several graphs after it has started up. Depending on the options we select, when starting the profiling session, it shows us the memory, garbage collector activity, recorded throughput, CPU load and many more.

 For example, this is the memory graph for the startup of a service.

For someone who has never dealt with a memory leak, looking at this graph, he might think that a memory leak occurred as the memory usage is increasing continuously. But if we look at the GC Activity graph during the startup period the memory increases, we observe that the garbage collector did not run (activity at 0%). Only at the end of the startup period we can see a spike in the garbage collector graph and a dramatic decrease in used memory. So, if we did not wait long enough for the garbage collector to run, we could come to the false conclusion that a memory leak occurred.

To get an idea of how a real memory leak looks like, we create an example on the basis of the Baeldung memory leak article. In our example, we want to load all Attributes of different Models of some kind. For the sake of this example, we generate the Model objects that contain the Attributes ourselves.

    private static List<Model.Attribute> getAllModelAttributes(){
        // Model is only used within this method
        List<Model> models = Model.getModelList();

        return models.stream()
                .map(Model::getAttribute)
                .collect(Collectors.toList());
    }

Let’s look at the GC Activity graph; there are many spikes during the shown duration. This means that the garbage collector tries to free memory, but if we look at the memory graph at the time the spikes occur, it looks like the garbage collector does not succeed as no memory is freed. This could have reasons other than a memory leak,  e.g. a large cache to speed up attribute access. However, we know that our application does not have such a cache and the high memory usage is quite suspicious. To be sure that we have to deal with a memory leak, let’s look at all the objects that are currently in memory.

To find out what objects are currently in memory, we use the “Live Memory / All Objects” view. From the the first two rows, it shows that we have a large number of instances of our Model and Model.Attribute class, and that they together consume 2.4 GB of memory. Ok, now we know what takes up the memory. Unfortunately, when looking at our code, we don’t know where the Model instances are still referenced. As we only extract the Attribute of the Model and after that, it is not used anymore and therefore, should be picked up by the garbage collector. By taking a heap snapshot, we can further analyse the memory problem. Looking at the “Incoming References” we can use the “Show Paths to GC Root” function to identify why the garbage collector cannot handle these instances.

Now we look for the first entry that looks familiar to us; in this case, it is the io.viesure.Model$Attribute. This means that the Model.Attribute objects hold a reference to a Model object and now we have to investigate why this is the case. 

To find the issue, we look at our Model.Attributes class and how these objects get instantiated. In this case, the problem is that the developer defined the Attribute class as a non-static nested class, a so-called inner class.

public class Model {
    ...
    public class Attribute{
        ...
    }
}

An inner class has access to the instance members of the containing class instance and therefore needs a reference to its containing class. For our use case, we don’t need this behaviour, so we simply change the inner class to a static nested class.

public class Model {
    ...
    public static class Attribute{
        ...
    }
}

Let’s profile our application again and see if the memory consumption changed.

Comparing the new memory graph with the old one, we observe that the memory usage is a lot lower than before, decreasing from around 3 GB to 1.5 GB. We can also record the objects and check if we can still find instances of our Model class.

Hurray! No more unnecessary Model instances that eat our memory.

We successfully found the memory leak and decreased the memory used by our application significantly. Of course, this was only a small example, on how to use JProfiler to analyze our applications and most of the time it is a lot harder to spot a memory leak, but this blog gave a short introduction which tools can help to identify a memory leak.

Elias Dräxler, Software Engineer at viesure

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Close Menu