Top 10 Things To Do With GraalVM
By: Chris Seaton
Tags: Java, Compilers, Programming, JavaScript, Ruby

This post has been updated to GraalVM 19.3.0

There are a lot of different parts to GraalVM, so if you’ve heard the name before, or even seen some of our talks, there are for sure things that it can do that you don’t know about yet. In this article we’ll list some of the diverse features of GraalVM and show you what they can do for you.

You can reproduce everything that I’m showing in this article with GraalVM 19.3.0, which is available from graalvm.org/downloads. I’m using the Enterprise Edition on macOS, which is free to evaluate as we’re doing here, but the instructions will also work on Linux. Most of them will also work with the Community Edition.

Follow along and run these programs while you’re reading! The code I’m running on GraalVM can be cloned from github.com/chrisseaton/graalvm-ten-things/.

Setup

I’ve downloaded GraalVM Enterprise Edition based on JDK8 for macOS from https://www.oracle.com/downloads/graalvm-downloads.html, and put the programs from it onto my $PATH . This gives me the Java and JavaScript languages by default.



$ cd foo

$ tar -zxf graalvm-ee-java8-darwin-amd64-19.3.0.tar.gz

# or graalvm-ee-java8-linux-amd64-19.3.0.tar.gz on Linux

$ export PATH=graalvm-ee-java8-19.3.0/Contents/Home/bin:$PATH

# or PATH=graalvm-ee-java8-19.3.0/bin:$PATH on Linux $ git clone https://github.com/chrisseaton/graalvm-ten-things.git $ cd foo$ tar -zxf graalvm-ee-java8-darwin-amd64-19.3.0.tar.gz# or graalvm-ee-java8-linux-amd64-19.3.0.tar.gz on Linux$ export PATH=graalvm-ee-java8-19.3.0/Contents/Home/bin:$PATH# or PATH=graalvm-ee-java8-19.3.0/bin:$PATH on Linux

GraalVM comes with JavaScript included and has a package manager called gu that lets you install additional languages. I’ve installed the Ruby, Python and R languages. I’ve also installed the native-image tool. These all get downloaded from GitHub.

$ gu install native-image

$ gu install ruby

$ gu install python

$ gu install R

Now when you run java or js you'll get the GraalVM versions of those runtimes.

$ 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) $ js --version

GraalVM JavaScript (GraalVM EE Native 19.3.0)

1. High-performance modern Java

The Graal name in the GraalVM comes from the GraalVM compiler. GraalVM is one compiler to rule them all, meaning that it’s a single implementation of a compiler written as a library which can be used for many different things. For example we use the GraalVM compiler to compile both ahead-of-time and just-in-time, to compile multiple programming languages, and to multiple architectures.

One simple way to use GraalVM is to use it as your Java JIT compiler.

We’ll use this example program, which gives you the top-ten words in a document. It uses modern Java language features like streams and collectors.

GraalVM includes a javac compiler, but it isn't any different from the standard one for the purposes of this demo, so you could use your system javac instead if you wanted to.

$ javac TopTen.java

If we run the java command included in GraalVM we'll be automatically using the Graal JIT compiler - no extra configuration is needed. I'll use the time command to get the real, wall-clock elapsed time it takes to run the entire program from start to finish, rather than setting up a complicated micro-benchmark, and I'll use a large input so that we aren't quibbling about a few seconds here or there. The large.txt file is 150 MB.

$ make large.txt

$ time java TopTen large.txt

sed = 502701

ut = 392657

in = 377651

et = 352641

id = 317627

eu = 317627

eget = 302621

vel = 300120

a = 287615

sit = 282613 real 0m12.950s

user 0m17.827s

sys 0m0.622s

GraalVM is written in Java, rather than C++ like most other JIT compilers for Java. We think this allows us to improve it more quickly than existing compilers, with powerful new optimisations such as partial escape analysis that aren’t available in the standard JIT compilers for HotSpot. This can make your Java programs run significantly faster.

To run without the GraalVM JIT compiler to compare, I can use the flag -XX:-UseJVMCICompiler . JVMCI is the interface between GraalVM and the JVM. You could also compare against your standard JVM as well.

$ time java -XX:-UseJVMCICompiler TopTen large.txt

sed = 502701

ut = 392657

in = 377651

et = 352641

id = 317627

eu = 317627

eget = 302621

vel = 300120

a = 287615

sit = 282613 real 0m19.602s

user 0m20.357s

sys 0m0.498s

This shows GraalVM running our Java program in around two-thirds of the wall-clock time it takes to run it with a standard HotSpot compiler. In an area where we are used to treating single-digit percentage increases in performance as significant, this is a big-deal.

You’ll still get a result better than HotSpot if you use the Community Edition, but it won’t be quite as a good as the Enterprise Edition.

Twitter are one company using GraalVM in production today, and they say that for them it is paying off in terms of real money saved. Twitter are using GraalVM to run Scala applications — GraalVM works at the level of JVM bytecode so it works for any JVM language.

This is the first way you can use GraalVM — simply as a drop-in better JIT compiler for your existing Java applications.

2. Low-footprint, fast-startup Java

The Java platform is particularly strong for long-running processes and peak performance, but short-running processes can suffer from longer startup time and relatively high memory usage.

For example, if we run the same application with a much smaller input — around 1 KB instead of 150 MB, then it seems to take an unreasonably long time, and quite a lot of memory at 70 MB, to run for such a small file. We use -l to print the memory used as well as time used.

$ make small.txt

$ /usr/bin/time -l java TopTen small.txt

# -v on Linux instead of -l

sed = 6

sit = 6

amet = 6

mauris = 3

volutpat = 3

vitae = 3

dolor = 3

libero = 3

tempor = 2

suscipit = 2

0.17 real 0.28 user 0.04 sys

70737920 maximum resident set size

...

GraalVM gives us a tool that solves this problem. We said that GraalVM is like a compiler library and it can be used in many different ways. One of those is to compile ahead-of-time, to a native executable image, instead of compiling just-in-time at runtime. This is similar to how a conventional compiler like gcc works.

$ native-image --no-server --no-fallback TopTen

[topten:37970] classlist: 1,801.57 ms

[topten:37970] (cap): 1,289.45 ms

[topten:37970] setup: 3,087.67 ms

[topten:37970] (typeflow): 6,704.85 ms

[topten:37970] (objects): 6,448.88 ms

[topten:37970] (features): 820.90 ms

[topten:37970] analysis: 14,271.88 ms

[topten:37970] (clinit): 257.25 ms

[topten:37970] universe: 766.11 ms

[topten:37970] (parse): 1,365.29 ms

[topten:37970] (inline): 3,829.55 ms

[topten:37970] (compile): 34,674.51 ms

[topten:37970] compile: 41,412.71 ms

[topten:37970] image: 2,741.41 ms

[topten:37970] write: 619.13 ms

[topten:37970] [total]: 64,891.52 ms

This command produces a native executable called topten . This executable isn’t a launcher for the JVM, it doesn’t link to the JVM, and it doesn’t bundle the JVM in any way. native-image really does compile your Java code, and any Java libraries you use, all the way down to simple machine code. For runtime components like the garbage collector we are running our own new VM called the SubstrateVM, which like GraalVM is also written in Java.

If we look at the libraries which topten uses you can see they are only standard system libraries. We could also move just this one file to a system which has never had a JVM installed and run it there to verify it doesn’t use a JVM or any other files. It’s also pretty small - this executable is less than 8 MB.

$ otool -L topten # ldd topten on Linux

topten:

libSystem.B.dylib (current version 1252.250.1)

CoreFoundation (current version 1575.12.0)

/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)

$ du -h topten

7.5M topten

If we run the executable we can see that it starts around an order of magnitude faster, and uses around an order of magnitude less memory, than running the same program on the JVM does. It’s so fast that you don’t notice the time taken when using it at the command line — you don’t feel that pause you always get when running a short-running command with the JVM.

$ /usr/bin/time -l ./topten small.txt

sed = 6

sit = 6

amet = 6

mauris = 3

volutpat = 3

vitae = 3

dolor = 3

libero = 3

tempor = 2

suscipit = 2

0.02 real 0.00 user 0.00 sys

3158016 maximum resident set size

...

The native-image tool has some restrictions such as all classes having to be available during compilation, and some limitations around reflection. It has some additional advantages over basic compilation as well in that static initializers are run during compilation, so you can reduce the work done each time an application loads.

This is a second way that you can use GraalVM — a way to distribute and run your existing Java programs with a low-footprint and fast-startup. It also frees you from configuration issues such as finding the right jar files at runtime, and allows you to have smaller Docker images.

3. Combine JavaScript, Java, Ruby, and R

As well as Java, GraalVM includes new implementations of JavaScript, Ruby, R and Python. These are written using a new language implementation framework called Truffle that makes it possible to implement language interpreters that are both simple and high performance. When you write a language interpreter using Truffle, Truffle will automatically use GraalVM on your behalf to give you a JIT compiler for your language. So GraalVM is not only a JIT compiler and ahead-of-time native compiler for Java, it can also be a JIT compiler for JavaScript, Ruby, R and Python.

The languages in GraalVM aim to be drop-in replacements for your existing languages. For example we can install a Node.js module:

$ npm install color

...

+ color@3.1.1

added 6 packages from 6 contributors and audited 7 packages in 6.931s

We can write a little program color.js using this module to convert an RGB HTML color to HSL:

var Color = require('color'); process.argv.slice(2).forEach(function (val) {

console.log(Color(val).hsl().string());

});

Then we can run that in the usual way:

$ node color.js '#42aaf4'

hsl(204.89999999999998, 89%, 60.8%)

The languages in GraalVM work together — there’s an API which lets you run code from one language in another. This lets you write polyglot programs — programs written in more than one language.

You might want to do this because you want to write the majority of your application in one language, but there’s a library in another language’s ecosystem that you’d like to use. For example, say we’d like to write our application for converting a CSS color name to hexadecimal in Node.js, but we want to use a Ruby color library instead to do the conversion.

We specify some Ruby code to run as a string, but notice that we don’t do much in it — we just require the libraries, and then return a Ruby object. The way to use this object from Ruby is normally to say Color::RGB.by_name(name).html . If you look at how color_rgb is used further down by JavaScript, you can see that actually we're calling these methods from JavaScript, even though they are Ruby objects and methods, and we pass them a JavaScript string, and we concatenate the result, which is a Ruby string, with other JavaScript strings.

We’ll install both our Ruby and JavaScript dependencies.

$ gem install color

Fetching: color-1.8.gem (100%)

Successfully installed color-1.8

1 gem installed $ npm install express

+ express@4.17.0

added 50 packages from 37 contributors and audited 143 packages in 22.431s

We then need to run node with a couple of options: --polyglot to say we want access to other languages, and --jvm because the node native image by default doesn't include more than JavaScript.



serving at $ node --polyglot --jvm color-server.jsserving at http://localhost:8080

Then open http://localhost:8080/css/aquamarine, or some other colour name, as normal in your browser.

Let’s try a larger example using more languages and modules.

JavaScript doesn’t have a great solution for arbitrarily-large integers. I found several modules like big-integer but these are all inefficient as they store components of the number as JavaScript floating point numbers. Java's BigInteger class is more efficient so let's use that instead to do some arbitrarily-large integer arithmetic.

JavaScript also doesn’t include any built-in support for drawing graphs, where R does include excellent support for this. Let’s use R’s svg module to draw a 3D scatter plot of a trigonometric function.

In both cases we can use GraalVM’s polyglot API, and we can just compose the results from these other languages into JavaScript.

Open http://localhost:3000/ in your browser to see the result.

That’s the third thing we can do with GraalVM — run programs written in multiple languages and use modules from those languages together. We think of this as a kind of commoditisation of languages and modules — you can use whichever language you think is best for your problem at hand, and whichever library you want, no matter which language it came from.

4. Run native languages on the JVM

Another language that GraalVM supports is C. GraalVM can run C code in the same way that it runs languages like JavaScript and Ruby.

What GraalVM actually supports is running the output of the LLVM toolchain — LLVM bitcode — rather than directly supporting C. This means you can use your existing tooling with C, and also other languages that can output LLVM, such as C++, Fortran, and potentially other languages in the future. To make things simple for a demo I’m running a special single-file version of gzip, maintained by Stephen McCamant. It’s just the gzip source code and the autoconf configuration concatenated into one file for simplicity. I've also had to patch a couple of things to make it work on macOS and with clang, but not to get it working on GraalVM.

Then we can compile using standard clang (the LLVM C compiler), and we want it to compile to LLVM bitcode, not native assembly, because that's what GraalVM can run. I'm using clang 4.0.1.

$ clang -c -emit-llvm gzip.c

And then we run this directly using GraalVM using the lli command (LLVM bitcode interpreter). Let's try compressing a file using my system gzip , and then decompress using gzip running on GraalVM.

$ cat small.txt

Lorem ipsum dolor sit amet...

$ gzip small.txt

$ lli gzip.bc -d small.txt.gz

$ cat small.txt

Lorem ipsum dolor sit amet...

Alternatively, C/C++ code can be compiled to LLVM bitcode using the clang shipped with GraalVM. For that you should enable a pre-built LLVM toolchain support and point the LLVM_TOOLCHAIN environment variable to the directory containing a set of build tools, such as a C compiler and a linker, that enables compiling a native project to bitcode.

$ gu install llvm-toolchain

$ export LLVM_TOOLCHAIN=$(lli --print-toolchain-path)

Then you can compile gzip.c source code to an executable with embedded LLVM bitcode and run it as follows:

$ $ LLVM_TOOLCHAIN/clang gzip.c -o gzip

$ gzip small.txt

$ lli gzip -d small.txt.gz

$ cat small.txt

Lorem ipsum dolor sit amet...

The implementations of Ruby and Python in GraalVM use this technique to run C extensions for these languages. This means that you can run C extensions inside the VM, and it allows us to maintain high performance even while supporting these legacy native extension interfaces.

This is a fourth thing you can do with the GraalVM — run programs written in native languages like C and C++, and also run C extensions to languages like Python and Ruby, which existing JVM implementations like JRuby are not able to do.

5. Tools that work across all languages

If you program in Java you’re probably used to very high quality tools like IDEs, debuggers and profilers. Not all languages have these kind of tools, but you get them if you use a language in GraalVM.

All the GraalVM languages (except for Java at the moment) are implemented using the common Truffle framework. This allows us to implement functionality like debuggers once and have it available to all languages.

To try this we’ll write a basic FizzBuzz program, because it prints things to the screen and it has clear branches that are only taken on some iterations, so we can set some breakpoints more easily. We’ll start with a JavaScript implementation.

We can run this JavaScript program as normal using GraalVM, using the js executable.

$ js fizzbuzz.js

1

2

Fizz

4

Buzz

Fizz

...

We can also run the program with the flag --inspect . This will give us a link to open in Chrome and will pause the program in the debugger.

$ js --inspect fizzbuzz.js

Debugger listening on port 9229.

To start debugging, open the following URL in Chrome:

chrome-devtools://devtools/bundled/inspector.html?ws=127.0.0.1:9229/6c478d4e-1350b196b409

...

We can then set a breakpoint on the FizzBuzz line and then continue execution. When it breaks we’ll see the value of n , and can continue again, or explore the rest of the debugging interface.

The Chrome debugger is usually used with JavaScript, but there’s nothing special about JavaScript in GraalVM. This flag is also available and working in our implementations of Python, Ruby and R. I won’t show you the source of each program, but you run them in exactly the same way, and get the same Chrome debugger interface to each.

$ graalpython --inspect fizzbuzz.py

$ ruby --inspect fizzbuzz.rb

$ Rscript --inspect fizzbuzz.r

Another tool that you may be familiar with using already from Java is VisualVM. It gives you a user interface which you can connect to a running JVM on your machine or somewhere over a network to inspect various aspects such as how it is using memory and threads.

GraalVM includes VisualVM with the standard jvisualvm command.

$ jvisualvm &> /dev/null &

If we run it while we run our Java TopTen application from before, we can watch the memory use over time, or we can do something like take a heap dump and inspect what kind of objects we have using memory in our heap.

$ java TopTen large.txt

I’ve written this Ruby program to generate some garbage over time.

If you run a standard JVM language, like JRuby, with VisualVM you’ll be disappointed in that you’ll see the underlying Java objects, rather than any information about your language’s objects.

If we use the GraalVM version of Ruby instead, VisualVM will recognise the Ruby objects themselves. We need to use the --jvm command to use VisualVM, as it doesn't support the native version of Ruby.

$ ruby --jvm render.rb

We can see the same heap view dump of underlying Java objects if we want to, or under Summary we can select Ruby Heap and see proper Ruby objects instead.

The Truffle framework is a kind of nexus for languages and tools. If you program your languages using Truffle, and you program your tools like this debugger against Truffle’s tool API, then each tool works with each language, and you only have to write the tool once.

So the fifth way that you can use GraalVM is as a platform to get high quality tooling for languages which don’t always have the support behind them to build bespoke tools like the Chrome Debugger or VisualVM.

6. Extend a JVM-based application

As well as being usable as standalone language implementations, and together in a polyglot use case, these languages and tools can also be embedded in your Java application. A new org.graalvm.polyglot API lets you load and run code in other languages and to use values from them.

If you use the javac and java commands from GraalVM, the imports org.graalvm... will already be on your classpath, so you can compile and run this without any extra flags.

$ javac ExtendJava.java

$ java ExtendJava '14 + 2'

16

$ java ExtendJava -js 'Math.sqrt(14)'

3.7416573867739413

$ java ExtendJava -python '[2**n for n in range(0, 8)]'

[1, 2, 4, 8, 16, 32, 64, 128]

$ java ExtendJava -ruby '[4, 2, 3].sort'

[2, 3, 4]

These versions of the languages are the same high-performance polyglot versions that you get from using the commands like node and ruby as the GraalVM executables.

This is a sixth way you can use GraalVM — as a single interface to embed many different languages in your Java application. The polyglot API allows you to take guest language objects and use them as Java interfaces and other sophisticated interoperability.

7. Extend a native application

GraalVM already includes one native library built like this — it’s a library that lets you run code written in any GraalVM language from native applications. JavaScript runtimes like V8, and Python interpreters like CPython, are often embeddable meaning that they can be linked as a library into another application. GraalVM lets you use any language in an embedded context, by linking to this one polyglot embedding library.

The library is already built when you get GraalVM, but by default it only includes the builtin language JavaScript. You can rebuild the polyglot library to include other languages using the command below, but you’ll need to download Oracle GraalVM Enterprise Edition Native Image Early Adopter based on JDK8 for MacOS (19.3.0) from OTN. Rebuilding does take a few minutes, so you may want to just experiment with JavaScript if you’re following along — you don’t need to rebuild if you just want JavaScript.

$ gu install --force --file native-image-installable-svm-svmee-java8-darwin-amd64-19.3.0.jar

$ gu rebuild-images libpolyglot

We can write a simple C program that runs commands in any GraalVM language passed on the command line. We’re going to be doing the equivalent of our ExtendJava example from above, but with C as the host language.

We can then compile and run that using our system C compiler and link to the native polyglot library in GraalVM. Again, it doesn’t need a JVM.