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
- 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.
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:
Then create a new class and add the following example:
Once this is done we can create a fat application containing all the dependencies. For this I’ve chosen to use the
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
./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.
-class-path build/libs/ceres-inventory-all-deps-0.0.1.jar \
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.
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:
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!
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
With the native executable:
/usr/bin/time -f “\nmaxRSS\t%MkB” ./inventory
As you can see the memory usage went from 215.924kB to 18.104kB 👊
Although I have only tried this with
Spark, other web frameworks such as
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.