Java Heap Dump Analysis with Examples

I am a big fan of Java Memory Management and in this article, I will try to explain how to take and analyze heap dump with examples, but let’s refresh our minds and remember what we know about this domain. After some theoretical information, we will take a heap dump and will analyze it for a simple application.

What is Heap?

Whenever you create an object, it will store in a memory area which is called Heap for JVM applications. As you may guess, since you have limited capacity for the heap, someone should maintain the objects in the heap, this is called Garbage Collector. Garbage Collector collects unreferenced objects on the heap and applies some algorithm to make them destroyed to always have available memory in the heap. Once you have no available space in the heap, you will end up with;

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Garbage Collector only collects unreferenced objects, but sometimes there might be referenced objects but they are never used in the application. This kind of usage always causes a memory leak in the application. In order to detect this kind of problem and understand what is going on in the heap, we take a heap dump. Let’s take a look at how to take a heap dump for a specific application.

Capturing Heap Dump

If you read the article about Java Thread Dump Analysis, you will be very familiar with capturing heap dump in this section. In order to capture heap dump yourself, I prepared an example here, and you can execute the same commands on your computer whenever you see some Gradle execution examples. I will show 3 types of capturing heap dump in this article; VisualVM, jmap, and automatic heap dump with JVM options. Let’s slowly deep dive into each method after you clone the example project.

About Example Project

You can clone the code sample project here, and once you navigate heapdumpanalysis folder you will see the following classes in the java source folder.

package com.huseyin.heapdumpanalysis;

import java.util.ArrayList;
import java.util.List;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ProductCatalogService {

    public List<String> getProducts(int limit) {
        List<String> products = new ArrayList<>();
        for (int i = 0; i< limit; i++){
            products.add(new String(new char[1024*1000])); // 1 MB string
        }
        return products;
    }
}

This class is for generating a list of products ( actually they are just strings 🙂 ) and each product has a 1MB size. And second class is,

package com.huseyin.heapdumpanalysis;

import java.util.Arrays;
import java.util.List;

public class HeapDump {

    public static void main(String[] args) throws InterruptedException {
        int count = Integer.parseInt(args[0]);
        int waitTime = Integer.parseInt(args[1]);
        System.out.println("Loading products...");
        List<String> products = new ProductCatalogService().getProducts(count);
        System.out.println(products.size() + " products are loaded into memory.");
        Thread.sleep(waitTime * 1000L);
    }
}

This is for running the application to generate a list of products that will consume memory with an amount you provide in Gradle command arguments as follows.

JAVA_TOOL_OPTIONS=-Xmx200m \
./gradlew :heapdumpanalysis:run \
-PmainClass=com.huseyin.heapdumpanalysis.HeapDump \
--args="180 30"

JAVA_TOOL_OPTIONS lets you provide JVM arguments so that Gradle can pick up and use it during JVM fork operation. In our case, we provide -Xmx200m which means, the maximum heap capacity will be 200 MB. Moreover, if you have referenced objects with a size > ~ 200MB, you will get java.lang.OutOfMemoryError exception and the application will quit. I used ~200MB since the heap also contains some classes from Java Runtime, and 200-capacity(JRE Classes) will be your total capacity for the objects in your application.

--args is a list of arguments we can read from the java application with args[0], and here the first item is used for the size of products. For example, we generate 180 products which means 180 MB of data (1M for each product). The second item is for wait time in seconds. 30 means, that once the application generates products, it will wait 30 seconds before finishing execution. This is for letting you capture the heap dump of the running application. Now that we know how to execute the application, let’s take a look at how we can capture heap dump by using Visual VM.

Capturing Heap Dump with VisualVM

VisualVM is a visual tool integrating command-line JDK tools and lightweight profiling capabilities. It is designed for both development and production time use.

Visual VM Homepage

Once you download Visual VM from here, you can see the running JVM applications by opening VisualVM and checking it as follows.

Visual VM Start Screen

Once you click on the application which is running after Gradle execution, you will see the metadata of the application and click the Monitor tab to see Heap usage.

Heap Usage

The Blue section in the chart is the used part of the heap, and the brown part is the available heap memory for your application. If you want to capture a heap dump, you can click the Heap Dump button on the top right corner of the Monitor page. You will see it will popup another window with some heap dump insights as follows.

Heap Dump Insights – 1
Heap Dump Insights – 2
  1. You can see some aggregated insights about size, class count, instances, etc.
  2. In this part, Visual VM shows us the count of instances for each class type. As you can see byte[] has the biggest count of instances in this application.
  3. This section shows each instance with its size. As you can see, each of them is 1024 bytes which is 1MB, this is our product.
  4. This part shows the environmental information of your computer and about java distribution.
  5. This is similar to section 2 but this time it is ordered by size of instances instead of count of instances

After we saw capturing heap dump visually, let’s take a look the command line version.

Capturing Heap Dump with jmap

jmap comes with JDK distribution and you can capture heap dump by finding the process id of JVM first and then capture heap dump as follows.

List JVM Processes

Now to capture heap dump

jmap -dump:live,file=/tmp/heapdump.hprof 49417

This will capture heap dump and will store results at /tmp/heapdump.hprof. Here we request from jmap command to capture heap dump with -dump option and we tell capture only live object with :live option. jmap also takes file parameter to store results at the specified location. Finally, it accepts the JVM process id to analyze the heap. So what is next? What we will do with this heapdump.hprof file? Easy, you can load that file into VisualVM as follows.

Load
Only hprof files

The rest of the operation is the same as we show in the previous section.

VisualVM is simple for basic operations, but I also have another favorite tool MAT (Eclipse Memory Analyzer), and this tool also deserves a separate article to understand best practices :). You can check their documentation here.

Automatic Heap Dump Analysis

Until this part, we always captured heap dump manually. However, in real life, it might not be a valid strategy to take a manual heap dump for following reasons;

  • You may have tens of hundreds of JVM applications
  • The application might be crashed already

What if there would be a way to capture heap dump automatically once JVM throws OutOfMemoryError? You are lucky 🙂 JVM provides us a way to capture heap dump automatically with the following options.

-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heapdump.hprof

In order to apply it to our application, you can use the following Gradle command.

JAVA_TOOL_OPTIONS="-Xmx200m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof" \
./gradlew :heapdumpanalysis:run \
-PmainClass=com.huseyin.heapdumpanalysis.HeapDump \
--args="210 30"

Since we provide 200MB heap capacity and try to generate a product list with a total amount of 210MB, it will end up OutOfMemoryError, and JVM will take the heap dump of the existing heap, then will store it in /tmp/heapdump.hprof file.

Automatic Heap Dump Analysis

Especially, for the cloud-native environments where your JVM process lives in a container (tens or hundreds of them for microservices), it would be a good idea to configure this parameter to automatically take heap dump. However, you need to mount a special folder to those containers, or else, the heap dump result file will be gone from the container since it restarts 🙂

Conclusion

It is important to have good observability of the JVM process to understand how it goes with your application. In order to understand how it goes, you can take a look at the heap where all the java objects are located. We use capturing heap dump techniques and you can select one or more of them to apply to your existing JVM application ecosystem. See you in the next Java article 🙂

As always, you can clone the project we use in this article here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s