Memory statistics for running Java application

This post explains how to use OpenJDK CLI utility jmap

Posted by Vipin Sharma on Tuesday, June 1, 2021 Tags: OpenJDK tools   8 minute read

OutOfMemoryError is one of the most annoying problem java developers face. We get this error when the Java application is trying to add new objects to the heap and there is not enough space available. OpenJDK provides command-line utility jmap to troubleshoot memory related problems in a running Java application. The jmap command-line utility prints memory related statistics for a running JVM. That includes class loader statistics, information on objects awaiting finalization, histogram of the java object heap, and finally complete heap dump as well.

Reducing memory utilization is a big concern for JVM developers as well. Following are 2 important OpenJDK projects that aims to reduce the overall memory footprint of a Java application:

  1. Project Lilliput: Developers at RedHat started project Lilliput for this. This project aims to reduce size of Java object headers in the Hotspot JVM from 128 bits to 64 bits or lesser, overall reducing Java’s memory footprint and improving performance.
  2. Project Valhalla: This OpenJDK project improves Java’s memory density by making it easy to create compact, cache efficient data structures.

In this post, we will learn how to use different functionalities provided by jmap with help of examples.


Troubleshooting a running Java Application with jmap


jmap command

jmap [options] pid

options:    This represents the jmap command-line options.
pid:        The process ID for which the information specified by the options is to be printed.


Java program we will be analyzing in this post

This is Java program we will run to understand different features available in jmap

public class Test
{
   public static void main(String[] args)
   {
      while(true)
      {
      }
   }
}


For all our examples we will be using Java 17, as of writing this post it is built using JDK master branch. This post can help you to build JDK from the source.

java -version
openjdk version "17-internal" 2021-09-14
OpenJDK Runtime Environment (build 17-internal+0-adhoc.vipin.jdk)
OpenJDK 64-Bit Server VM (build 17-internal+0-adhoc.vipin.jdk, mixed mode)

Running Java process:

java Test

After running this program, we will print process id using jcmd. For more details on jcmd see this post.

jcmd

Output:

3712 Test
8255 jdk.jcmd/sun.tools.jcmd.JCmd

pid 3712 is the java process ID, we will use this for all our examples in this post.


Printing class loader statistics of Java heap

This command connects to a running process and prints class loader statistics of Java heap:

jmap -clstats 3712

Output:

lassLoader         Parent              CLD*               Classes   ChunkSz   BlockSz  Type
0x0000000800051bb0  0x0000000000000000  0x00007f8ee038bcd0       2      1152       813  jdk.internal.loader.ClassLoaders$PlatformClassLoader
0x0000000000000000  0x0000000000000000  0x00007f8ee017a9f0     997    996864    938941  <boot class loader>
                                                                56     21760     13257   + hidden classes
0x0000000800051868  0x0000000800051bb0  0x00007f8ee02ae990       1       768       253  jdk.internal.loader.ClassLoaders$AppClassLoader
Total = 3                                                     1056   1020544    953264  
ChunkSz: Total size of all allocated metaspace chunks
BlockSz: Total size of all allocated metaspace blocks (each chunk has several blocks)


Printing information on objects awaiting finalization

This command connects to a running process and prints information on objects awaiting finalization.

jmap -finalizerinfo 3712

Here is the output, it matches our expectation since our Java application is not creating objects that are eligible for finalization.

No instances waiting for finalization found


Printing histogram of java object heap

The majority of memory leak problems can be easily identified using Java object histogram. We can take histogram a couple of times and analyze the number of objects created in this duration. In this way, we can identify anomalies in the number of Objects created.

This command connects to a running process and prints a histogram of the Java object heap. Here we are using live sub-option to counts only live objects.

jmap -histo:live 3712

Output:

num     #instances         #bytes  class name (module)
-------------------------------------------------------
1:          8437         449688  [B (java.base)
2:          7952         190848  java.lang.String (java.base)
3:          1084         132720  java.lang.Class (java.base)
4:          3298         105536  java.util.HashMap$Node (java.base)
5:           809          72200  [Ljava.lang.Object; (java.base)
6:           340          55232  [Ljava.util.HashMap$Node; (java.base)
...
...

Following is the part of jmap --help command, it shows the other histogram options available.

histo-options:
  live         count only live objects (takes precedence if both "live" and "all" are specified)
  all          count all objects in the heap (default if one of "live" or "all" is not specified)
  file=<file>  dump data to <file>
  parallel=<number>  parallel threads number for heap iteration:
                              parallel=0 default behavior, use predefined number of threads
                              parallel=1 disable parallel heap iteration
                              parallel=<N> use N threads for parallel heap iteration

It is not quick and easy to analyze huge heap dump files on local desktop due to memory limitation, this is where histogram feature becomes very helpful.


Taking Java process Heap dump

When the histogram can not help, we can take the full heap dump from the Java application and analyze it using external tools like Eclipse MAT. This command connects to a running Java process and dumps the heap.

jmap -dump:live,format=b,file=heap.bin 3712

Output:

Dumping heap to /home/vipin/heap.bin ...
Heap dump file created [3384326 bytes in 0.017 secs]

Following is the part of jmap --help command, that provides other dump options available.

dump-options:
  live         dump only live objects (takes precedence if both "live" and "all" are specified)
  all          dump all objects in the heap (default if one of "live" or "all" is not specified)
  format=b     binary format
  file=<file>  dump heap to <file>
  gz=<number>  If specified, the heap dump is written in gzipped format using the given compression level.
               1 (recommended) is the fastest, 9 the strongest compression.


JDK16 feature Taking gzipped heap dump

What is the first thing we do after taking a heap dump? we compress the big dump file that makes it easy to transfer on a personal computer. JDK-16 introduced gz option to take gzipped heap dump.

gz=1 is fastest and recommended compression level. Below is example command:

jmap -dump:live,format=b,file=heap.bin_1,gz=1 3712

Output:

Dumping heap to /home/vipin/heap.bin_1 ...
Heap dump file created [771184 bytes in 0.029 secs]

gz=9 is generating the smallest file. Below is example command:

jmap -dump:live,format=b,file=heap.bin_9,gz=9 3712

Output:

Dumping heap to /home/vipin/heap.bin_9 ...
Heap dump file created [600388 bytes in 0.586 secs]


Conclusion

jmap is a very useful command to check memory statistics of a running Java application. Knowing jmap features like histogram saves a lot of time in troubleshooting Java application memory issues. jmap is part of JDK itself, no need to install any third party software.

If you want to get amazing Java jobs, I wrote an ebook 5 steps to Best Java Jobs. You can download this step-by-step guide for free!