Speed up application launch time with GraalVM

Introduction to GraalVM

In the past few months we have made great progress in regards to infrastructure. Where we started with manual configured applications running on compute engines and Elastic Beanstalk, we are now running multiple Kubernetes clusters with the entire infrastructure in code, ready to be re-deployed again.

During the dockerization and setting up the Kubernetes deployments we learned that:

  • Java needs some tweaking to work properly with Docker 
    Some optimizations have been made in Java 10 that are enabled by default in Java 11 -XX:-UseContainerSupport️.
  • Startup time of your application is crucial to allow fast scaling.
  • The overall memory footprint of applications/frameworks can be quite large nowadays.

On a quest to look for solutions to these problems, and while secretly looking for new things because new is always better, I encountered GraalVM. Which is a replacement Java Hotspot VM (JVM) that is developed by Oracle. It’s also noteworthy that the the Graal compiler has been made available as an experimental option in JDK 10.

New is always better — Barney Stinson — How I met your mother

GraalVM

GraalVM is a high performance embeddable polyglot virtual machine that supports applications written in JavaScript, Python, Ruby, R, JVM-based languages like Java, Scala, Clojure, Kotlin, and LLVM-based languages such as C and C++.

SubstrateVM (part of GraalVM) is a native virtual machine that allows you to compile your Java programs ahead-of-time into a native executable. The resulting program does not run on the Java HotSpot VM, but uses necessary components like memory management, thread scheduling from a different implementation of a virtual machine, called Substrate VM. Substrate VM is written in Java and compiled into the native executable. The resulting program has significantly faster startup time and lower run-time memory overhead compared to a Java VM.

How does this work?

GraalVM uses aggressive static analysis during the ahead-of-time compilation which requires a closed-world assumption. This means that all the required classes should be reachable at run time. Therefore using frameworks that depend heavy on reflection are relatively painful/impossible to convert to native images with GraalVM. Substrate VM has partial support for reflection which is resolves through static analysis by looking for code that calls the reflection API.

Real life scenario

Now lets try running simple Kotlin API that exposes product inventory from a Redis back-end. For this simple exercise I’ve chosen the Spark library due to it’s ease of use and minimal boilerplate.

A complete example with all the code included can be found in this Github repository.

Lets start with adding dependencies:

build.gradle.kts dependency section used for this post

Then create a new class and add the following example:

Sample application using Redis/Spark

Once this is done we can create a fat application containing all the dependencies. For this I’ve chosen to use the com.github.johnrengelman.shadow plugin.

Just make sure to have a Redis instance running on localhost. I’m using a Docker container for just this purpose:

docker run -p 6379:6379 --name some-redis -d redis

Test if the application is working by running the fatJar command and then run the application using java.

./gradlew clean fatJar
java -jar build/libs/ceres-inventory-all-deps-0.0.1.jar
2018–12–03 18:33:43 INFO EmbeddedJettyServer:127 — == Spark has ignited …
2018–12–03 18:33:43 INFO EmbeddedJettyServer:128 →> Listening on 0.0.0.0:4567
2018–12–03 18:33:43 INFO Server:444 — Started @463ms

Awesome that works! 🎆

Finally we can use the command to build the native executable. I used graalvm-ce-1.0.0-rc9-linux-adm64 for this post.

native-image \
-class-path build/libs/ceres-inventory-all-deps-0.0.1.jar \
-H:EnableURLProtocols=http \
-H:IncludeResources=”log4j.properties” \
-H:Name=inventory \
-H:Class=inventory.ApplicationKt \
-H:+ReportUnsupportedElementsAtRuntime \
-H:+AllowVMInspection

Warning: running the command above could take some time and might result in some high temperatures 🔥 🔥. During this step, SubstrateVM will compile your application using it’s ahead-of-time compiler. This is a CPU/memory intensive process.

Startup time

Once the application has been compiled you will see that a binary file has been created. Just run the application like any other by running:

./inventory
2018–12–03 16:04:30 INFO EmbeddedJettyServer:127 — == Spark has ignited …
2018–12–03 16:04:30 INFO EmbeddedJettyServer:128 →> Listening on 0.0.0.0:4567
2018–12–03 16:04:30 INFO Server:444 — Started @7ms

The app went from starting in 463ms to a whopping 7ms, awesome!

Memory footprint

The other problem that we’re trying to tackle is the memory requirement for the application. To measure the memory footprint, I use a simple tool called /usr/bin/time. You can read more about it here. The results are astonishing!

With the Java Hotspot VM:

/usr/bin/time -f “\nmaxRSS\t%MkB” java -jar build/libs/ceres-inventory-all-deps-0.0.1.jar
maxRSS 215924kB

With the native executable:

/usr/bin/time -f “\nmaxRSS\t%MkB” ./inventory
maxRSS 18104kB

As you can see the memory usage went from 215.924kB to 18.104kB 👊

Afterthoughts
Although I have only tried this with Spark, other web frameworks such as Micronaut and Vert.x also seem to support GraalVM with some minor tweaking. Spring however depends deeply on reflection which makes it hard/impossible to use with GraalVM for now (compilation to a native image that is).

The issue with reflection will be a major problem for many existing applications. But GraalVM can be a great addition for new applications (and many new Kotlin applications).

In this post we skimmed over the surface of GraalVM and SubstrateVM. Personally I am pretty positive about the results and I am a big fan of the option to create native executables.

Related links