Troubleshooting HotSpot JVM

jcmd

jcmd is a tool for executing various diagnostic operations.

jmap

jmap is an unsupported tool for memory operations.

histo

The histo option may be used to print a histogram of Java objects by class, including number of instances and number of bytes. The live option only counts reachable objects, although it does force a full GC first. Example:

$ jmap -histo 15078
 num     #instances         #bytes  class name
----------------------------------------------
   1:           399        4747704  [I
   2:          1565         151240  [C
   3:           450          51456  java.lang.Class
   4:           194          48144  [B
   5:          1229          29496  java.lang.String ...

jinfo

jinfo is an unsupported tool which prints Java configuration of a live Java process or from a core dump.

Thread Dump

HotSpot Java can produce a thread dump which details the activity of each thread. For example:

2020-01-01 01:23:45
Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.25-b01 mixed mode):

"pool-1-thread-8402" prio=3 tid=0x000000010956f000 nid=0x3cff waiting on condition [0xfffffff7868fe000]
  java.lang.Thread.State: TIMED_WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0xfffffffe90fb54a0> (a java.util.concurrent.SynchronousQueue$TransferStack)
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:226)
    at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460)
    at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:359)
    at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:942)
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:724)...

The "nid" is the hexadecimal native thread ID.

Request Thread Dump

  1. On POSIX operating systems, send the SIGQUIT signal and replace $PID and a text thread dump will be printed to stderr:
    kill -3 $PID
  2. Use jcmd and replace $PID and a text thread dump is printed to the jcmd console:
    jcmd PID Thread.print

HPROF Heapdumps

An HPROF heapdump contains the full Java heap object graph as well as Java object memory contents (for example, Strings, primitives, etc.). This is used for investigating OutOfMemoryErrors, tuning Java heap usage, etc.

By default, when a Java memory request cannot be fulfilled, an OutOfMemoryError is thrown, but an HPROF dump is not produced. Use -XX:+HeapDumpOnOutOfMemoryError to produce an HPROF dump in this condition. Consider tuning -XX:GCTimeLimit and -XX:GCHeapFreeLimit to control when an OutOfMemoryError is thrown. -XX:HeapDumpPath may be used to control where the dumps are written to. -XX:OnOutOfMemoryError may be used to execute an operating system command on an OOM.

To analyze heapdumps, see the Eclipse Memory Analyzer Tool chapter.

Generating HPROF heapdumps

Additional methods of requesting heap dumps are documented in the Troubleshooting WAS chapter.

  1. An HPROF heapdump is automatically produced on OOM after adding -XX:+HeapDumpOnOutOfMemoryError (this must be explicitly configured and the JVM restarted as it's not enabled by default). Example error output in stdout:
    java.lang.OutOfMemoryError: GC overhead limit exceeded
    Dumping heap to java_pid28537.hprof ...
  2. Use jcmd and replace $PID:
    jcmd $PID GC.heap_dump heapdump.hprof
  3. Use the HotSpotDiagnostic.dumpHeap operation
  4. Use jmap and replace $PID:
    jmap -dump:format=b,file=heapdump.hprof ${PID}
  5. Produce an operating system core dump (see the Troubleshooting Operating Systems chapter) and then extract the HPROF heapdump using jmap:
    jmap -dump:format=b,file=heapdump.hprof ${PATH_TO_JAVA} ${PATH_TO_CORE}
  6. Use -XX:OnOutOfMemoryError (see below)
  7. From within Eclipse Memory Analyzer Tool: File } Acquire Heap Dump

Use DTrace to Produce Stacks Calling certain Methods

The following DTrace script prints stacks when System.gc is called. The JVM must be started with -XX:+ExtendedDTraceProbes

Attach the DTrace script to a running PID: /usr/sbin/dtrace -qs methodtrace.d -p ${PID}

It is often the case that the overhead of such a DTrace script is very high and may be inappropriate for a production environment.

#pragma D option bufsize=128m

dtrace:::BEGIN
{
    /* Java level tracing */
    traceJava = 1;       /* 1 = enable, 0 = disable */
    classFilter = "java/lang/System";    /* e.g. "java/util"        */
    methodFilter = "gc";   /* e.g. "get"              */
    
    /* Initialise the per-thread indentation variable */
    self->indent = 0;
}

hotspot$target:::method-entry
/
    traceJava
    && (classFilter == "" || strstr(copyinstr(arg1, arg2), classFilter) != NULL)
    && (methodFilter == "" || strstr(copyinstr(arg3, arg4), methodFilter) != NULL)
/
{
    self->indent += 2;

    wt = walltimestamp;
    printf("%Y.%09d: %d/%d:%*s-> %s.%s%s\n",
           wt,
           wt % 1000000000,
           pid,
           tid,
           self->indent,
           "",
           copyinstr(arg1, arg2),
           copyinstr(arg3, arg4),
           copyinstr(arg5, arg6));    
    jstack(500, 8192);    
}

Example output:

2018 Jan 10 14:03:24.280004000: 18021/2:
-> java/lang/System.gc()V
libjvm.so`__1cNSharedRuntimeTdtrace_method_entry6FpnKJavaThread_pnNmethodOopDesc__i_+0x1ac
              java/lang/System.c
              SystemDemo.ai
              0xfbc0021c
libjvm.so`__1cJJavaCallsLcall_helper6FpnJJavaValue_pnMmethodHandle_pnRJavaCallArguments_pnGThread__v_+0x318
              libjvm.so`jni_CallStaticVoidMethod+0x67c
              libjli.so`JavaMain+0x740
              libc.so.1`_lwp_start

Use -XX:OnOutOfMemoryError to Spawn jmap

#!/bin/sh
# Usage:
#  1. Create oom.sh with the contents of this script
#  2. Change paths in the "Variables" section if needed
#  3. chmod a+x oom.sh
#  4. Run java with the following argument, replacing $PATH with path to oom.sh
#     -XX:OnOutOfMemoryError="/$PATH/oom.sh %p"

# Variables
LOCKFILE=/tmp/oomlock
OUT=/tmp/oomout.txt
NOW=`date +"%Y%m%d_%H%M%S"`
CURDIR=`pwd`
JAVA_HOME=/opt/IBM/WebSphere/AppServer/java/

# Execution
echo "OOM handler script started for PID $1 at $NOW in $CURDIR" >> $OUT
if [ ! -f $LOCKFILE ]; then
  touch $LOCKFILE >> $OUT 2>&1
  NOW=`date +"%Y%m%d_%H%M%S"`
  echo "OOM handler requested hprof at $NOW" >> $OUT
  FILENAME="heap_${NOW}_${1}.hprof"
  $JAVA_HOME/bin/jmap -F -dump:format=b,file=$FILENAME $1 >> $OUT 2>&1
  # /usr/bin/gcore -F -o core_$NOW.dmp $1 >> $OUT 2>&1
  CODE=$?
  echo "OOM handler returned with $CODE at $NOW" >> $OUT
  rm -f $LOCKFILE >> $OUT 2>&1
fi
NOW=`date +"%Y%m%d_%H%M%S"`
echo "OOM handler finished at $NOW" >> $OUT

Use -XX:OnOutOfMemoryError to Spawn gcore

#!/bin/sh
# Usage:
#  1. Create oom.sh with the contents of this script
#  2. Change paths in the "Variables" section if needed
#  3. chmod a+x oom.sh
#  4. Run java with the following argument, replacing $PATH with path to oom.sh
#     -XX:OnOutOfMemoryError="/$PATH/oom.sh %p"
#  5. After an OOM occurs, check /tmp/oomout.txt for output of the command
#  6. Run `jmap -dump:format=b,file=heap.hprof ${PATH_TO_JAVA} ${CORE}`
#
# Notes:
#  OnOutOfMemoryError runs /usr/bin/sh -c $CMD synchronously, during which it
#  appears it has a lock that prevents jmap to attach. Tried -F, but that
#  generated an infinite loop and ran into other issues, so running gcore
#  instead.

# Variables
LOCKFILE=/tmp/oomlock
OUT=/tmp/oomout.txt
NOW=`date +"%Y%m%d_%H%M%S"`
CURDIR=`pwd`
GCORE_PATH=/usr/bin/gcore

# Execution
echo "OOM handler script started for PID $1 at $NOW in $CURDIR" >> $OUT
if [ ! -f $LOCKFILE ]; then
  touch $LOCKFILE >> $OUT 2>&1
  NOW=`date +"%Y%m%d_%H%M%S"`
  echo "OOM handler requested hprof at $NOW" >> $OUT
  # $JAVA_HOME/bin/jmap -dump:format=b,file=heap_$1.hprof $1 >> $OUT 2>&1
  $GCORE_PATH -F -o core_$NOW.dmp $1 >> $OUT 2>&1
  CODE=$?
  echo "OOM handler returned with $CODE at $NOW" >> $OUT
  rm -f $LOCKFILE >> $OUT 2>&1
fi
NOW=`date +"%Y%m%d_%H%M%S"`
echo "OOM handler finished at $NOW" >> $OUT

Code to Request Diagnostics from within the JVM

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class Play
{
    public static void main(String... args) throws Throwable
    {
        System.out.println("Requesting core...");
        tryGenerateCore();
    }

    public static void tryGenerateCore()
    {
        try
        {
            String requestedFileName = generateCore();
            if (requestedFileName != null)
            {
                System.out.println("Started writing core dump to " + requestedFileName);
            }
        }
        catch (Throwable t)
        {
            System.out.println("Error generating core: " + t.getLocalizedMessage());
            t.printStackTrace();
        }
    }

    private final static boolean ENABLE_REQUESTING_COREDUMPS = Boolean.getBoolean("ENABLE_REQUESTING_COREDUMPS");
    private final static SimpleDateFormat DIAG_NAME_FORMAT = new SimpleDateFormat("yyyyMMdd.HHmmss");
    private final static String CORE_PROGRAM_PATH = System.getProperty("CORE_PROGRAM_PATH", "/usr/bin/gcore");
    private final static int MAX_CORE_DUMPS = Integer.getInteger("MAX_CORE_DUMPS", 1);
    private static final AtomicInteger coreDumpsTaken = new AtomicInteger();
    private static int coreDumpsRequested;

    /**
     * Disabled by default. Enable with -DENABLE_REQUESTING_COREDUMPS=true
     * <p />
     * Request a non-destructive core dump in a separate thread by spawning out
     * to the gcore command. gcore will attach to and pause the process, dump
     * all virtual memory (so the size will be about the size in ps VSZ) and
     * then the process should continue. Unlike an OOM or using jmap to request
     * an HPROF dump, requesting a core does not request a Full GC. Jmap can be
     * used to extract an HPROF heapdump from the core:
     * <p />
     * <code>$ jmap -dump:format=b,file=heap.hprof ${PATH_TO_java} ${CORE}</code>
     * <p />
     * Whereas asking the JVM to generate a heapdump with jmap is a complex
     * operation because the JVM has to walk all the data structures, the
     * operating system generating a core is very simple: the OS just pauses the
     * process and dumps out all of the virtual memory. The overhead of a core
     * file is almost completely in writing the large amount of bytes to disk.
     * There are some techniques to make this very fast. First, if there is
     * sufficient filecache in RAM (i.e. a large amount of free RAM), then the
     * OS will write the core to RAM and then asynchronously write to disk, thus
     * making the pause quite fast. However, this can have some performance side
     * effects. An alternative way to do this is to mount a RAMdisk and write
     * the core to a RAMdisk.
     * <p />
     * Warning: ensure sufficient core, file and other ulimits. Also ensure
     * sufficient disk space in the current working directory.
     *
     * @return null if -DMAX_CORE_DUMPS (default 1) has been reached or
     *         -DENABLE_REQUESTING_COREDUMPS=false; otherwise, the requested
     *         core file name.
     * @throws IOException
     * @throws InterruptedException
     */
    public static synchronized String generateCore() throws IOException, InterruptedException
    {
        if (!ENABLE_REQUESTING_COREDUMPS || coreDumpsRequested++ >= MAX_CORE_DUMPS) { return null; }
        CoreDumpThread coreDumpThread = new CoreDumpThread();
        coreDumpThread.start();
        return coreDumpThread.getRequestedFileName();
    }

    public static int getPID()
    {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (name != null)
        {
            int x = name.indexOf('@');
            if (x != -1)
            {
                name = name.substring(0, x);
                return Integer.parseInt(name);
            }
        }
        throw new RuntimeException("Could not find PID");
    }

    static class CoreDumpThread extends Thread
    {
        private final int pid;
        private final String requestedFileName;
        private Throwable error;

        public CoreDumpThread()
        {
            super("CoreDumpThread : " + coreDumpsTaken.get());
            // Writing the core can take a while, so we'll prefer to block the
            // JVM
            setDaemon(false);
            pid = getPID();
            requestedFileName = "core." + DIAG_NAME_FORMAT.format(new Date()) + "." + pid + ".dmp";
        }

        @Override
        public void run()
        {
            try
            {
                ProcessBuilder processBuilder = new ProcessBuilder(CORE_PROGRAM_PATH, "-o", requestedFileName, "" + pid);
                processBuilder.redirectErrorStream(true);
                Process process = processBuilder.start();
                BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));

                String line;
                StringBuilder sb = new StringBuilder();
                while ((line = br.readLine()) != null)
                {
                    sb.append(line);
                }
                int exitValue = process.waitFor();
                if (exitValue == 0)
                {
                    coreDumpsTaken.incrementAndGet();
                }
                else
                {
                    System.out.println("Error requesting core. Exit value " + exitValue + ". Output " + sb.toString());
                }
            }
            catch (Throwable t)
            {
                error = t;
                System.out.println("Error generating core: " + t.getLocalizedMessage());
                t.printStackTrace();
            }
        }

        public String getRequestedFileName()
        {
            return requestedFileName;
        }

        public Throwable getError()
        {
            return error;
        }
    }
}