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:
- 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.
- 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!