Generating Heap Dumps from Native Images

GraalVM allows to create highly performing native applications with the the Native Image technology. It is also possible to generate heap dumps of the native image processes to monitor the execution. This functionality is available only with GraalVM Enterprise.

Native Image does not implement JVMTI agent and it is not possible to trigger heap dump creation using tools like Visual VM or jmap. You can build the image for your application in a way so that it can handle certain signals and then get a heap dump when the application receives the USR1 signal (other supported signals are QUIT/BREAK for stackdumps and USR2 to dump runtime compilation info). You only need to build your image with GraalVM Enterprise Native Image and use the -H:+AllowVMInspection option. Another possibility is to write a special method which will generate a heap dump at certain points in the lifetime of your application. For example, when certain conditions are met while running the native image, your application code can trigger heap dump creation. A dedicated org.graalvm.nativeimage.VMRuntime#dumpHeap API exists for this purpose. We will show both possibilities in this chapter.

Handle SIGUSR1 Signal #

The following Java example is a simple multi-threaded application which runs for 60 seconds. There is enough time to get its PID and send the SIGUSR1 signal which will generate a heap dump into the application’s working directory. Save the following code as SVMHeapDump.java file on your disk:

import java.text.DateFormat;
import java.util.Date;

public class SVMHeapDump extends Thread {
    static int i = 0;
    static int runs = 60;
    static int sleepTime = 1000;
    @Override
    public void run() {
        System.out.println(DateFormat.getDateTimeInstance().format(new Date()) + ": Thread started, it will run for " + runs + " seconds");
        while (i < runs){
            System.out.println("Sleeping for " + (runs-i) + " seconds." );
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException ie){
                System.out.println("Sleep interrupted.");
            }
            i++;
        }
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws InterruptedException {
        // Do some manipulations so we have something to dump
        StringBuffer sb1 = new StringBuffer(100);
        sb1.append(DateFormat.getDateTimeInstance().format(new Date()));
        sb1.append(": Hello GraalVM native image developer! \nGet PID of this process: ");
        sb1.append("'ps -C svmheapdump -o pid= '\n");
        sb1.append("then send it signal: ");
        sb1.append("'kill -SIGUSR1 <pid_printed_above>' \n");
        sb1.append("to get heap dump generated into working directory.\n");
        sb1.append("Starting thread!");
        System.out.println(sb1);
        SVMHeapDump t = new SVMHeapDump();
        t.start();
        while (t.isAlive()) {
            t.join(0);
        }
        sb1 = new StringBuffer(100);
        sb1.append(DateFormat.getDateTimeInstance().format(new Date()));
        sb1.append(": Thread finished after: ");
        sb1.append(i);
        sb1.append(" iterations.");
        System.out.println(sb1);
    }
}

Build a Native Image #

Compile SVMHeapDump.java as following:

$ $JAVA_HOME/bin/javac SVMHeapDump.java

If you run it on java, you will see it runs for 60 seconds then finishes. We will now look how to tell native-image to generate binary which will accept SIGUSR1 signal to produce a heap dump.

Build a native executable with GraalVM Enterprise Native Image and provide the -H:+AllowVMInspection option for the builder:

$ $JAVA_HOME/bin/native-image SVMHeapDump -H:+AllowVMInspection
Build on Server(pid: 41691, port: 61250)
[svmheapdump:41691]    classlist:     412.03 ms,  2.52 GB
[svmheapdump:41691]        (cap):   1,655.34 ms,  2.52 GB
[svmheapdump:41691]        setup:   2,741.18 ms,  2.52 GB
[svmheapdump:41691]     (clinit):     190.08 ms,  2.59 GB
[svmheapdump:41691]   (typeflow):   5,231.29 ms,  2.59 GB
[svmheapdump:41691]    (objects):   6,489.13 ms,  2.59 GB
[svmheapdump:41691]   (features):     203.11 ms,  2.59 GB
[svmheapdump:41691]     analysis:  12,394.98 ms,  2.59 GB
[svmheapdump:41691]     universe:     425.55 ms,  2.59 GB
[svmheapdump:41691]      (parse):   1,418.69 ms,  2.59 GB
[svmheapdump:41691]     (inline):   1,289.94 ms,  2.59 GB
[svmheapdump:41691]    (compile):  21,338.61 ms,  2.62 GB
[svmheapdump:41691]      compile:  24,795.01 ms,  2.62 GB
[svmheapdump:41691]        image:   1,446.14 ms,  2.62 GB
[svmheapdump:41691]        write:   5,482.12 ms,  2.62 GB
[svmheapdump:41691]      [total]:  47,805.47 ms,  2.62 GB

The native-image tool analyzes existing SVMHeapDump.class and creates from it an executable file. When the command completes, svmheapdump is created in the current directory.

Run the Application and Get the Heap Dump #

Run the application:

$ ./svmheapdump
May 15, 2020, 4:28:14 PM: Hello GraalVM native image developer!
Get PID of this process: 'ps -C svmheapdump -o pid= '
then send it signal: 'kill -SIGUSR1 <pid_printed_above>'
to get heap dump generated into working directory.
Starting thread!
May 15, 2020, 4:28:14 PM: Thread started, it will run for 60 seconds

Open the 2nd terminal to get the process ID of the running svmheapdump application using a command like ps -C svmheapdump -o pid= for Linux OS and pgrep svmheapdump for macOS. Copy the printed process ID, e.g. 100, and use it to send the signal to the running application:

kill -SIGUSR1 100

The heap dump will be available at the working directory while application continues to run.

Generate the Heap Dump from within a Java Application #

The following Java example shows how a heap dump can be generated from within a running Java application using VMRuntime.dumpHeap() after some condition is met. The condition to generate a heap dump is provided as an option on the command line. Save following Java code as SVMHeapDumpAPI.java.

The application creates some data to have something to dump, checks the command line to see if heap dump has to be created and then in method createHeapDump() creates the actual heap dump performing checks for file existence.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import org.graalvm.nativeimage.VMRuntime;

public class SVMHeapDumpAPI {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // Do some manipulations so we have something to dump
        StringBuffer sb1 = new StringBuffer(100);
        sb1.append(DateFormat.getDateTimeInstance().format(new Date()));
        sb1.append(": Hello GraalVM native image developer. \nYour command line options are: ");
        String liveArg = "true";
        if (args.length > 0) {
            sb1.append(args[0]);
            System.out.println(sb1);
            if (args[0].equalsIgnoreCase("--heapdump")){
                if(args.length > 1 ) {
                  liveArg = args[1];
                }
                createHeapDump(Boolean.valueOf(liveArg));
            }
        } else {
            sb1.append("None");
            System.out.println(sb1);
        }
     }

    /**
     * Generate heap dump and save it into temp file
     */
     private static void createHeapDump(boolean live) {
     try {
         File file = File.createTempFile("SVMHeapDump-", ".hprof");
         VMRuntime.dumpHeap(file.getAbsolutePath(), live);
         System.out.println("  Heap dump created " + file.getAbsolutePath() + ", size: " + file.length());
     } catch (UnsupportedOperationException unsupported) {
         System.out.println("  Heap dump creation failed." + unsupported.getMessage());
     } catch (IOException ioe) {
         System.out.println("IO went wrong: " + ioe.getMessage());
     }
 }
}

Building a Native Image #

In the next step, compile SVMHeapDumpAPI.java:

$ $JAVA_HOME/bin/javac SVMHeapDumpAPI.java

Then build a native executable with GraalVM Enterprise Native Image:

$ $JAVA_HOME/bin/ native-image SVMHeapDumpAPI
Build on Server(pid: 41691, port: 61250)
[svmheapdumpapi:41691]    classlist:     447.96 ms,  2.53 GB
[svmheapdumpapi:41691]        (cap):   2,105.64 ms,  2.53 GB
[svmheapdumpapi:41691]        setup:   3,010.19 ms,  2.53 GB
[svmheapdumpapi:41691]     (clinit):     178.51 ms,  2.61 GB
[svmheapdumpapi:41691]   (typeflow):   9,153.49 ms,  2.61 GB
[svmheapdumpapi:41691]    (objects):   9,170.40 ms,  2.61 GB
[svmheapdumpapi:41691]   (features):     347.67 ms,  2.61 GB
[svmheapdumpapi:41691]     analysis:  19,208.00 ms,  2.61 GB
[svmheapdumpapi:41691]     universe:     390.40 ms,  2.61 GB
[svmheapdumpapi:41691]      (parse):   1,519.70 ms,  2.63 GB
[svmheapdumpapi:41691]     (inline):   1,072.87 ms,  2.63 GB
[svmheapdumpapi:41691]    (compile):  36,028.90 ms,  2.61 GB
[svmheapdumpapi:41691]      compile:  40,595.67 ms,  2.61 GB
[svmheapdumpapi:41691]        image:   2,384.57 ms,  2.61 GB
[svmheapdumpapi:41691]        write:   3,161.35 ms,  2.63 GB
[svmheapdumpapi:41691]      [total]:  69,300.73 ms,  2.63 GB

When the command completes, the svmheapdumpapi executable is created in the current directory.

Run the Application and Check the Heap Dump #

Now you can run your native image application and generate a heap dump from it with the output similar to one below:

$ ./svmheapdumpapi --heapdump
May 15, 2020, 4:06:36 PM: Hello GraalVM native image developer.
Your command line options are: --heapdump
  Heap dump created /var/folders/hw/s9d78jts67gdc8cfyq5fjcdm0000gp/T/SVMHeapDump-6437252222863577987.hprof, size: 8051959

The resulting heap dump can be then opened with the VisualVM tool like any other Java heap dump.