JVM Advent

The JVM Programming Advent Calendar

Getting started with GraalVM

Recently at a conference, I was approached by someone asking whether it’s true that GraalVM breaks reflection? Which of course it doesn’t. And I explained to them what GraalVM is, what it can do and how to maybe start experimenting with it. And they were happy to learn about it, and I was happy to help. It also inspired this article where I’ll try to describe exactly that and we’ll concentrate on the Java apps and Java developers:

  • What GraalVM is?
  • What can it do for your projects?
  • How to stay connected with the project?
What GraalVM is?

GraalVM is a high-performance embeddable polyglot virtual machine for applications written in JavaScript, Python, Ruby, R, JVM-based languages like Java, Scala, Groovy, Kotlin, Clojure and LLVM-based languages such as C and C++. The above feels like a lot, so let’s unpack this definition.

GraalVM is a runtime that supports different languages that is clear. It is a polyglot runtime, so it can run your applications with components written in several different languages at the same time. It is a high-performance virtual machine, so the idea is that your application is going to be fast. And it is embeddable meaning you can take the GraalVM technology and with the reasonable effort put in into your Java or native applications.

That’s quite a lot of positive qualities put into a single sentence, which is not an accident. At the heart of GraalVM compiler  – a state of the art compiler for the JVM bytecode that ties into all other abilities of GraalVM.

GraalVM integrates its compiler into the Java HotSpot VM – the JVM implementation from the OpenJDK project, and that’s how it can run programs written in the JVM languages. Do this download GraalVM for your operating system, unpack it, add it to the $PATH like shown bellow, set the $JAVA_HOME environment variable to point to it too. Windows support is currently experimental, so for Windows consider using the WLS and the linux build:

tar -xzf graalvm-ee-java8-linux-amd64-19.3.0.tar.gz
export GRAALVM_HOME=graalvm-ee-java8-19.3.0
export PATH=$JAVA_HOME/bin:$PATH
export JAVA_HOME=$GRAALVM_HOME

 

Very good, congratulations, you just obtained GraalVM. Let’s see what’s inside:

ls $GRAALVM_HOME/bin
ControlPanel  javadoc         jdeps       jsadebugd     orbd         tnameserv
appletviewer  javafxpackager  jhat        jstack        pack200      unpack200
extcheck      javah           jinfo       jstat         policytool   wsgen
gu            javap           jjs         jstatd        polyglot     wsimport
idlj          javapackager    jmap        jvisualvm     rmic         xjc
jar           javaws          jmc         keytool       rmid
jarsigner     jcmd            jmc.ini     lli           rmiregistry
java          jconsole        jps         native2ascii  schemagen
java-rmi.cgi  jcontrol        jrunscript  node          serialver
javac         jdb             js          npm           servertool

You can see there are normal Java utilities you can find in any JDK: javac, java, javap and so on. You can run the java command too:

java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit GraalVM EE 19.3.0 (build 25.231-b11-jvmci-19.3-b05, mixed mode)

When you run Java in GraalVM it starts the HotSpot VM with the top tier JIT compiler, which normally would be C2, replaced with the GraalVM compiler.

JVM does a lot of work before your code hits the top tier compiler, it finds, loads, verifies and initialises the classes, it holds metadata about the classes, it has garbage collection, it has, for example, an interpreter to run JVM bytecode which isn’t compiled yet. That’s a lot of functionality that isn’t the JIT compiler, and GraalVM builds on OpenJDK to have it. It also means that GraalVM can run Java programs the same way OpenJDK would – supporting reflection, proxies, classloading, finalizers, and so on. Try it for running your favorite java application?

Now one difference it has from other OpenJDK builds is the up-to-date GraalVM compiler enabled. GraalVM compiler is different from normally used C2, and it optimises code differently. One of the advantages of the GraalVM compiler is that it can happen often code that is using abstractions as well as the code that is written to avoid those.

For example, if we look at the benchmark like:

public class ObjectsHash {

    static int intField;
    static long longField;
    static double doubleField;

    @Benchmark
    public int abstracted() {
        return Objects.hash(intField, longField, doubleField);
    }

    @Benchmark
    public int raw() {
        return ((intField * 31) + Long.hashCode(longField)) * 31 + Double.hashCode(doubleField);
    }
}

In it there are two methods under test, the raw() and the abstracted() which both compute the hash of the fields in the object. If you run these using GraalVM, you’ll notice that there is a very small difference in the results between the two. And it is often surprising to developers, because they know that Objects.hash is a complex method. It takes varargs, which means creating an array, it then calls the Arrays.hashCode(), checks for null, iterates through the argument array checking elements for nulls too. Below you can see the actual code involved in the Objects.hash call.

Objects#hash
     public static int hash(Object... values) {
         return Arrays.hashCode(values);
     }

Arrays#hashCode
     public static int hashCode(Object a[]) {
         if (a == null)
             return 0;

         int result = 1;

         for (Object element : a)
             result = 31 * result + (element == null ? 0 : element.hashCode());

         return result;
     }

GraalVM compiler can “see” through all that complexity and produce the machine code with very similar performance to the version which is manually optimised not to have abstractions. On OpenJDK for example, the difference between the results of raw() and abstracted() benchmarks are much more pronounced with abstracted being several times slower than raw.This is one of the main interesting things about GraalVM, it can very often run your Java applications faster than other JDKs. It will work for the other JVM languages too, Twitter, for example, is using the compiler from GraalVM to significantly speed up their Scala services. Now running Java applications faster is the most direct application of the GraalVM compiler. There are others too, let’s look at them.

GraalVM compiler can be used as a JIT compiler in the JVM as we saw in the previous part of this article. It can also be used as an ahead-of-time (AOT) compiler.You have the option to install the native-image utility into GraalVM. Native image is marked as an Early Adopter technology, but it doesn’t mean that it’s not suitable to be used in production, which many people incorrectly believe. You can download the native-image component from the same page you got the base GraalVM distribution (be mindful to download the one for your operating system), and install it into GraalVM by using the gu utility, for example:

gu install -L ../native-image-installable-svm-svmee-java8-linux-amd64-19.3.0.jar

Now you have the native-image command in the $GRAALVM_HOME/bin directory. And you can feed it the bytecode of your application, and it’ll analyse that statically, compile it, and produce a single binary file that is your application. This binary file, usually called GraalVM native image of your application doesn’t depend on the JVM, can have startup in milliseconds rivalling the startup of C and go apps, and doesn’t need to compile code at runtime which allows it to use less memory at runtime. Here’s a small [example of the app](https://github.com/graalvm/graalvm-demos/tree/master/native-list-dir), which we for brevity won’t include in this article – but it’s a simple Java app gathering some details from the filesystem.

On my machine, measuring by the /usr/bin/time utility, native image of that app runs in milliseconds and consumes at maximum 5MB of RAM (not just the heap, the whole memory for the process).

/usr/bin/time -v ./listdir
Walking path: .
Total: 7 files, total size = 7055035 bytes
	Command being timed: "./listdir"
	User time (seconds): 0.00
	System time (seconds): 0.00
	Percent of CPU this job got: 50%
	Elapsed (wall clock) time (h:mm:ss or m:ss): 0:00.00
	Average shared text size (kbytes): 0
	Average unshared data size (kbytes): 0
	Average stack size (kbytes): 0
	Average total size (kbytes): 0
	Maximum resident set size (kbytes): 5288
…

The native image utility analyses your application statically, so some dynamic features like runtime proxies for example or the infamous reflection need to be configured by the developer. The most convenient way to do so is to run your application in the JIT mode, with a special java agent, which will trace usage of the features that require configuration and output the required configuration for you.

So you run your application with the following -agentlib option and in the META-INF/native-image directory you’ll see the required config. The good thing is that GraalVM can run your applications in the JIT mode, so you don’t even have to have another JDK at hand.

$GRAALVM_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image –jar yourapp.jar 

Now with this config, GraalVM native image will handle reflection, proxies, JNI, resources (the things that cannot be figured out using static analysis) with no problems.

There are a number of web frameworks which have figured out the native image configuration for you: Helidon, Micronaut, Quarkus. The work on Spring framework is ongoing and there already are examples of how it works.

In that video above Sébastien showed the Spring Petclinic sample app which uses JPA and H2 – so a lot of common Java technologies compiled as a GraalVM native image starts in less than 200 milliseconds!

You can easily build microservices with these frameworks and use GraalVM native image technology to make them better suited for the cloud deployments where startup times and memory usage are extremely important. The best part? You don’t have to, if your application doesn’t care about startup times, you can happily run it with GraalVM in the JIT mode and enjoy all the performance benefits the state-of-the-art JIT brings.

GraalVM also supports other languages, remember, it’s a polyglot virtual machine. JavaScript, including the ability to run node.js applications, is included in the base distribution (and an attentive reader could have noticed the `node` utility in the list of commands under $GRAALVM_HOME/bin earlier). Support for the other languages needs to be installed using the same gu utility we used above. This will give GraalVM even more options which applications it can run. For example, you can use GraalVM to do server-side rendering of react applications with performance similar to node.js, or run R code in your Scala code for data science tasks, but the exact details on how GraalVM runs other languages or embeddability of GraalVM are beyond the scope of this article which focuses on the JVM workloads.

 

Conclusion

In this article we looked at how to get started with GraalVM–which is a straight forward process, you can use it for Java apps after unpacking and adding to `$PATH`. We showed you an example benchmark which illustrates how when using GraalVM abstracted code doesn’t necessarily mean performance penalties. We installed an optional GraalVM native image component that helps to compile applications ahead-of-time to GraalVM native image which are superb for cloud deployments.

There is a lot of information about GraalVM online, and some of it is certainly outdated or misinformed, like breaking the reflection myth–reflection works great on GraalVM. You can use several to stay connected and get the latest information about GraalVM, you can find them on the project website: https://www.graalvm.org/community/, and one sure way to get all the latest news about GraalVM is by following it on Twitter: @GraalVM. And just think about it, when you click that follow button on Twitter, the code that handled it most probably runs with the help of GraalVM too.

 

 

 

Author: Oleg Shelajev

Next Post

Previous Post

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

© 2024 JVM Advent | Powered by steinhauer.software Logosteinhauer.software

Theme by Anders Norén