Use GraalVM Dashboard to Optimize the Size of a Native Executable

The GraalVM Dashboard is a web-based tool that visualizes the composition of a native executable. It displays the breakdown of packages, classes, and methods included in a native executable, and provides a visual summary of objects that contributed most to its heap size. The GraalVM Dashboard uses report files created by the native image builder. (For more information, see GraalVM Getting Started.)

This guide demonstrates how to use the dashboard to optimize the size of a native executable. It introduces two implementations of a “fortune” sample application that simulate the traditional fortune Unix program (for more information, see fortune).

Note: This guide assumes you have installed Maven.

Fortune

Package the contents of the first implementation (fortune) as a native executable and review its composition.

  1. Make sure you have installed a GraalVM JDK. The easiest way to get started is with SDKMAN!. For other installation options, visit the Downloads section.

  2. Download or clone the repository and navigate into the fortune-demo/fortune directory:
     git clone https://github.com/graalvm/graalvm-demos
    
     cd fortune-demo/fortune
    
  3. Build the project:
     mvn clean package
    
  4. When the build succeeds, run the application on the JVM with the Tracing agent. Since you have installed GraalVM, it will run on GraalVM JDK.
     mvn -Pnative -Dagent exec:exec@java-agent
    

    The application will return a random saying. The agent generates the configuration files in the target/native/agent-output subdirectory.

  5. Build a native executable of this application with GraalVM Native Image and Maven:
     mvn -Pnative -Dagent package
    

    When the command completes, a native executable, fortune, is generated in the /target directory of the project and ready for use.

  6. Run the application by launching a native executable directly:
     ./target/fortune
    

    The application should slowly print a random phrase.

    The application’s pom.xml file employs the Native Image Maven plugin to build a native executable, configured to produce diagnostic data using these two options:

     -H:DashboardDump=fortune -H:+DashboardAll
    

    These options result in a file named fortune.bgv. (For more information about different options, see Dumping the Data for GraalVM Dashboard.)

    Compare the sizes of the JAR file and the native executable (for example, using du):

     du -sh target/*
     0B	    target/archive-tmp
     136K	target/classes
     17M     target/fortune
     2.0M	target/fortune-1.0-jar-with-dependencies.jar
     32K	    target/fortune-1.0.jar
     44M	    target/fortune.bgv
     4.0K	target/fortune.build_artifacts.txt
     0B	    target/generated-sources
     4.0K	target/maven-archiver
     8.0K	target/maven-status
     0B	    target/test-classes
    

    The size of the JAR file is 2MB, compared to the 17MB size of the native executable. The increase in size is because the native executable contains all necessary runtime code as well as pre-initialized data in its heap.

  7. Open the GraalVM Dashboard and load the fortune.bgv file. (Click +, click Select File, select the fortune.bgv file from the target directory, and then click OK.)

    The GraalVM dashboard provides two visualizations of a native executable: code size breakdown and heap size breakdown. (For more information, see Code Size Breakdown and Heap Size Breakdown, respectively.)

    Code Size Breakdown View

    The screenshot above visualizes the code breakdown of the fortune native executable, a great part of which consists of the Jackson JSON parser library implemented in the package com.fasterxml. One approach to reduce the size of a native executable is to minimize the amount of space taken by code. The code size breakdown gives you an insight into the amount of space taken up by the packages that are included in your native executable.

    Furthermore, the screenshot below shows that the heap of the native executable contains 4MB of Bytes and almost 800KB of Strings. Another approach to reduce the size of a native executable is to minimize the size of its heap.

    Heap Size Breakdown View

    In the next section, we’ll consider an alternative implementation for the fortune application that reduces the amount of code and reduces the size of the heap.

Static Fortune

The first implementation of the fortune application uses the Jackson JSON parser (package com.fasterxml) at runtime to parse the contents of a resource file that the native image builder has included in the native executable. An alternative implementation (named “staticfortune”) parses the contents of the resource file at build time. This means that the resource file is no longer included in the executable, and the code required to parse the file is omitted from the native executable because it is only required at build time.

  1. Change to the project directory and build the project:
     cd ../staticfortune
    
     mvn clean package
    
  2. Run the application on the JVM (GraalVM JDK) with the Tracing agent:
     mvn -Pnative -Dagent exec:exec@java-agent
    

    The application will print a random saying. The agent generates the configuration files in the target/native/agent-output subdirectory.

  3. Build a static native executable:
     mvn -Pnative -Dagent package
    

    When the command completes, a native executable, staticfortune, is generated in the /target directory of the project and ready for use.

  4. Run the demo by launching a native executable directly or with the Maven profile:
     ./target/staticfortune
    

    The application should behave in exactly the same way as the first implementation.

    The application’s pom.xml file again uses the Native Image Maven plugin to build a native executable. However, for this implementation it adds an extra option to initialize class demo.StaticFortune at build time:

     -H:DashboardDump=staticfortune -H:+DashboardAll --initialize-at-build-time=demo.StaticFortune
    

    Compare the sizes of the JAR file and the native executable:

     du -sh target/*
     0B	    target/archive-tmp
     76K	    target/classes
     0B	    target/generated-sources
     4.0K	target/maven-archiver
     4.0K	target/maven-status
     4.3M	target/staticfortune
     2.0M	target/staticfortune-1.0-jar-with-dependencies.jar
     32K	    target/staticfortune-1.0.jar
     9.0M	target/staticfortune.bgv
     4.0K	target/staticfortune.build_artifacts.txt
     0B	    target/test-classes
    

    The size of the native executable has reduced in size from 17MB to 4.3MB.

    The reduction in size is due to the use of the --initialize-at-build-time= argument used with the Native Image Maven plugin.

  5. The build created a file named staticfortune.bgv. Load it into the GraalVM Dashboard to view the composition of the staticfortune native executable.

    Code Size Breakdown View

    The screenshot above demonstrates that the code in the com.fasterxml package is no longer present. There are also reductions in the amount of code included from the java.util, java.math, and java.text packages.

    The screenshot below illustrates that there has also been a significant reduction in the amount of heap given to Strings (767KB versus 184KB), and a reduction in Bytes from 4MB to 862KB.

    Heap Size Breakdown View