Stop Wasting Your Heap

The Java Virtual Machine is a wonderful little piece of software. It provides the illusion of an infinite heap – as long as you stop referring to instances you no longer need, you can keep going forever. Allocate as many little temporary object as you like; you do not have to worry about running out of memory. The JVM will automatically reclaim the memory when needed.

That said, the amount of memory that you reference at any given time, sometimes referred to as the live set, is limited by whatever memory is available to the Java heap. If you get an OutOfMemoryError, you either have a memory leak, or your application is trying to keep more memory live than can fit on the heap.

For solving the memory leak case, I will soon be demoing something pretty exciting at JavaOne 2017 that we’ve been working on (stay tuned). But what if the application is simply keeping alive too much memory? How do you go about reducing the amount of live memory required to run your application?

One way is to limit your application in various ways. Only allow N number of simultaneous users, crank down various caches you may have used in your application to speed up certain operations etc. Another is to try to look at various heap usage patterns that are known to waste heap memory. You usually end up doing a little bit of everything.

There is a little known experimental plug-in for Java Mission Control called JOverflow which can be helpful for estimating how much memory is wasted due to common heap usage anti-patterns.

Installing JOverflow

JOverflow can easily be installed from within JMC like this:

  1. Start JMC
  2. Go to Help | Install New Software
    SNAGHTML13a75ed2
  3. Check Heap Analysis/JOverflow
    image
  4. Click Next, Next, and finally accept the license and Finish

If you are installing into Eclipse, simply use the Experimental update site (can be found from the homepage, http://oracle.com/missioncontrol)

The Example

The example I will be using is a heap dump from an earlier version of Java Mission Control, JMC 4.1.0. In that version we sacrificed quite a lot of heap memory, for a dubious performance optimization.

The heap dump can be downloaded from here: jmc41dump.hprof

Having the example open in JOverflow will allow you to experiment with the UI, and will likely help facilitate the understanding of the rest of this blog post.

Understanding the JOverflow User Inteface

Start by opening a heap dump. If JOverflow is properly installed in JMC (or Eclipse, if running JMC in Eclipse), you should now be presented with the JOverflow user interface.

This is what it looks like on my computer, with the example heap dump (click pictures for full size):

image

There are four quadrants in the JOverflow user interface. Also notice the little reset button in the upper right corner (clip_image002). It will reset all the selections in the user interface.
 

Object Selection

The top left quadrant, Object Selection, will show you what heap usage anti-patterns the analysis has found. The first column in the Object Selection table show the kind of objects found, implicitly telling you what was analyzed for. The second how much memory they use in total. The third column, Overhead, shows how much of the memory was wasted, in percent of the total heap used.

SNAGHTML13c3d562

Referrer Tree-table

The top right quadrant contains the Referrer tree-table. This tree-table will show the aggregated reference chains for whatever is selected. Note that the way to reset the selections in the Referrer tree-table is to right click in the table. This is since you can make multiple consecutive selections to arrive at the reference chain you are interested in.

SNAGHTML13c68918

Note that the calculated overhead is all zero. This is since we have not selected the result of any analysis in the Object Selection table yet.

Class Histogram

The lower left shows a class histogram for whatever is selected, allowing you to filter on class.

SNAGHTML13cb55ea

Note that selections can be done directly in the pie chart. If you want to reset your selection, click the button representing the selection you have made.

SNAGHTML13cd5757

Again, note that the Overhead column is still zero. This is since we have not selected the result of any analysis in the Object Selection table. That said, by selecting something directly in the class histogram, without having something selected in the Object Selection table, we can see what analysis is relevant for the selected class.

Ancestor referrer

The final table, in the lower right, will show the objects grouped by the closest ancestor referrer.

SNAGHTML13d3cf97

It provides a pie chart to show the memory distribution, and filter box, making it easy to home in on instances of classes belonging to specific packages. Now let’s take a look at the actual example.

Reducing Memory Usage – Example

From the Object Selection table, it seems we are wasting 17% of our used heap with 710 sparse arrays. Selecting Sparse Arrays in the Object Selection table shows us a lot immediately:

image

Almost all of the sparse arrays are Object arrays (see class histogram) accounting for 16% of the heap. The Referrer table-tree and the Ancestor referrer table both show that the field CircularArray.m_array keep holding on 8 of these, and that these 8 instances are responsible for almost all of overhead. So, whilst there are indeed 710 sparse arrays in our system, just 8 are responsible for almost all the heap waste.

We want to know where these circular arrays are being used, so we click the top entry in the Referrer tree-table (remember to use right-click if you want to reset the selections you make here).

SNAGHTML13e08c36

So, it would seem that it is our JMX console attribute subscription storage that is wasting all that memory. This was implemented differently in a later release…

Let’s look at the second offender, the Duplicate Strings. First we reset the user interface (clip_image002). Then select Duplicate Strings in the Object Selection table. We immediately recognize that this will be a tougher job, and the law of diminishing returns probably would perhaps make us stop here. That said, it would seem like Eclipse interning some preference related keys, and JMC interning some JMC related Strings would help.

For a more fun example with duplicate strings see the following file: eclipse.hprof, where various duplicate path segment strings account for a lot of the memory waste:

image

Note: IIRC I tried this on a more recent version of Eclipse, and it was no longer an issue.

Summary

  • JOverflow is a plug-in for Java Mission Control
  • It can be used to identify potentially bad heap usage patterns
  • Used correctly it can reduce the amount of live memory required to run your application

3 Responses to "Stop Wasting Your Heap"

  1. Arjun says:

    Lovely article…we need more of this stuff…keep rocking

  2. Arjun says:

    Marcus, Just an FYI…
    I got the JMC/Joverflow add-in onto eclipse. I could analyze smaller heap.hprof dumps (less than a 1gig) without any issues, but when i tried it on a 3gig dump it threw the below error, the same 3 gig file opened in eclipse MAT and Java Visual VM ( bumped the -Xmx to 4 gig -Xmx4g). Its just that I like the layout of JOverflow and I wish we could open larger heap files.

    com.oracle.joverflow.heap.parser.DumpCorruptedException: exception caught: java.io.IOException: Stream Closed
    at com.oracle.joverflow.heap.parser.DumpCorruptedException$Runtime.(DumpCorruptedException.java:29)
    at com.oracle.joverflow.heap.model.JavaValueArray.readValue(JavaValueArray.java:107)
    at com.oracle.joverflow.heap.model.HeapStringReader.readCharElements(HeapStringReader.java:87)
    at com.oracle.joverflow.heap.model.HeapStringReader.readString(HeapStringReader.java:48)
    at com.oracle.joverflow.stats.StringStatsCollector.add(StringStatsCollector.java:84)
    at com.oracle.joverflow.stats.OverallStatsCalculator.calculate(OverallStatsCalculator.java:98)
    at com.oracle.joverflow.stats.StandardStatsCalculator.calculate(StandardStatsCalculator.java:43)
    at com.oracle.joverflow.ui.model.ModelLoader.run(ModelLoader.java:85)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
    cheers
    Arjun

  3. Marcus says:

    Hi Arjun,

    I opened this:
    https://bugs.openjdk.java.net/browse/JMC-6082

    Kind regards,
    Marcus

Leave a Reply

Your email address will not be published.