GraalVM Native Image – First Impressions

blank

Introduction

A native image is an operating system specific executable file. You can build such an image for basically every application running on a Java virtual machine. This approach promises faster start-up times and lower resource consumptions. This makes it appealing for serverless computing, auto-scaling platforms and command line tools.

I gained some impressions of this GraalVM technology while developing a standalone command line tool for formatting PL/SQL and SQL code. In this blog post I share some personal experiences and thoughts.

Starting Point

My starting point is an executable JAR.  I can run it from the command line via java -jar tvdformat.jar. The main class com.trivadis.plsql.formatter.TvdFormat calls a JavaScript format.js and passes all command line parameters to the JavaScript. Behind the scenes Oracle’s parser and formatter which are part of SQLcl and SQL Developer do the heavy lifting.

It’s quite obvious that this Java application loads a lot of classes and resources dynamically. The GraalVM’s native image builder can identify such objects with the tracing agent. Using the agent is simple. You start the Java application with an additional parameter. The idea is to run the application long enough to detect all dynamically loaded classes and resources. Technically, the trace agent intercept the calls involved in that dynamic loading process. It’s a best effort approach. It cannot guarantee completeness.

The next command shows how I run the formatter with the tracing agent for a small PL/SQL project:

This command formats 56 files and the trace agent produces 6 JSON configuration files in the config directory .

jni-config.json

Configuration file for parameter -H:JNIConfigurationFiles. See documentation.

predefined-classes-config.json

Configuration file for parameter -H:PredefinedClassesConfigurationFiles.

proxy-config.json

Configuration file for parameter -H:DynamicProxyConfigurationFiles. See documentation.

reflect-config.json

Configuration file for parameter --H:ReflectionConfigurationFiles. See documentation.

resource-config.json

Configuration file for parameter -H:ResourceConfigurationFiles. See documentation.

serialization-config.json

Configuration file for parameter -H:SerializationConfigurationFiles. “The serialization support ensures constructors for classes are contained in a native image, so that they can be deserialized in the first place”. See release notes of GraalVM 21.0.0.

Environment

The environment for this experiment was:

  • MacBook Pro (16-inch, 2021) with an Apple M1 Max chip and 64 GB memory running on macOS Monterey 12.0.1
  • GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05)
  • Apache Maven 3.8.3
  • SQLcl: Release 21.4.0.0 Production Build: 21.4.0.348.1716, installed in /usr/local/bin/sqlcl
  • Standalone PL/SQL & SQL Formatter at commit b4d26bd installed in $HOME/trivadis/plsql-formatter-settings
    • tvdformat.jar produced via mvn -DskipTests=true package in the standalone/target subdirectory
    • zip -d tvdformat-21.4.1-SNAPSHOT.jar "META-INF/native-image/*" to remove the native-image configuration files (they would be automatically used otherwise)
  • plscope-utils at commit 0687f5c installed in $HOME/github/plscope-utils

You find the configuration files used in this blog post in this Gist.

Building Image With Tracing Agent’s Config Files

Let’s try to build a native image with these configuration files.

Here’s the console output:

No error messages. Great. And what’s the size of the tvdformat executable? 115 MB. The --language:js parameter contributes about 98 MB, which includes probably a bit more than necessary .

Anyway, let’s run the native image.

This call produces the following console output:

The String class is used on line 23 in format.js. We need to register Java classes used in JavaScript and extend the configuration accordingly.

Extending Reflection Configuration (1)

I reviewed the format.js  and the JavaScript callback functions in trivadis_custom_format.arbori and created an addition configuration file reflect-config2.json for all Java classes used in JavaScript.

reflect-config2.json

Now we can build the native image with this additional configuration file.

The build completes without errors and produces a native image of 138 MB. 23 MB larger. Let’s run it.

The second run produces this console output:

An excellent error message. We need to configure the class java.util.function.Predicate via -H:DynamicProxyConfigurationFiles. Let’s do this.

Extending Dynamic Proxy Configuration

For this blog post I decided to create a second configuration file proxy-config2.json to distinguish it from the one generated by the trace agent.

Let’s build the native image with this additional configuration file.

The build completes without errors and produces a native image of 138 MB. The same size as before. Let’s run it.

The third run produces this console output:

This is quite interesting. The embedded format.js works now. The first file README.md does not contain SQL text blocks. and therefore the formatter was not called and no error was reported. But the formatter failed for the second file. What could be the reason for NumberFormatException: Cannot parse null string? – In this case the ParseNode class could not be loaded dynamically. To load this class successfully a lot of other classes are also required.

Extending Reflection Configuration (2)

Identifying all the dynamically loaded classes is not that simple. To debug the native image you can enable or add logging output, review the related source code or use a debugger. The native image debugger is an enterprise feature that is on the road map for the community edition. However, you still need to identify the reason for every single runtime exception. After adding the class to the configuration file you need to rebuild the native image and run it to detect the next exception. Doing this manually is really time-consuming.

Another approach is to register classes and their constructors, methods and fields programmatically using configuration with features. I’ve done that for a few chosen packages of the dbtools-common.jar that is part of the SQLcl installation. See source on GitHub.

For this blog post I created an additional configuration file with 218 classes.

reflect-config3.json