Learning to Use the GraalVM
The GraalVM is a handy virtual machine that supports multiple languages and enables native images for better performance.
Join the DZone community and get the full member experience.
Join For FreeIn the post "Truffle served in a Holy Graal: Graal and Truffle for polyglot language interpretation on the JVM," we got a brief introduction and a bit of deep dive into Graal, Truffle, and some of the concepts around it. But no technology is fun without diving deep into its practicality, otherwise it's like Theoretical Physics or Pure Maths — abstract for some, boring for others (sorry, the last part was just me ranting).
In this post, we will be taking a look into the GraalVM, by installing it, comparing SDK differences and looking at a some of the examples that illustrate how different languages can be compiled and run on the GraalVM, and also how they can be run in the same context and, finally, natively (more performant).
GraalVM is similar to any Java SDK (JDK) that we download from any vendor, except that it has JVMCI: Java-level JVM Compiler Interface support, and Graal is the default JIT compiler. It can not just execute Java code but also languages like JS, Ruby, Python, and R. It can also enable building ahead-of-time (AOT) compiled executables (native images) or share libraries for Java programs and other supported languages — although we won't be going through every language, only a select few of them.
Just to let you know, all of the commands and actions have been performed on an Ubuntu 16.04 operating system environment. They should work on MacOSX with minor adaptations, though on Windows, a more changes would be required.
Hands-On
We can get our hands on the GraalVM in more than one way, either building it on our own or downloading a pre-built version from a vendor website:
- Build on our own: some cloning and other magic (we can see that later on)
- Download a ready-made JVM: OTN download site
- Hook up a custom JIT to an existing JDK with JVMCI support (we can that see later on)
As we are using a Linux environment, it would be best to download and install the Linux (preview) version of GraalVM based on JDK8 (> 500MB file, need to Accept the license, need to be signed in on OTN or you will be taken to https://login.oracle.com/mysso/signon.jsp).
Follow the installation information on the download page. After unpacking the archive, you will find a folder by the name of graalvm-0.30
(at the time of writing of this post) when you execute the command below:
$ tar -xvzf graalvm-0.30-linux-amd64-jdk8.tar.gz
Eagle Eyeing: Compare SDKs
We will quickly check the contents of the SDK to gain some familiarity with it, so let's check the contents of the GraalVM SDK folder:
$ cd graalvm-0.30
$ ls
Which looks familiar and has similarities when compared with the traditional Java SDK folder (i.e. JDK 1.8.0_44):
$ cd /usr/lib/jdk1.8.0_44
$ ls
Except we have quite a few additional artifacts to learn about, i.e. the launchers on the VM for the supported languages like FastR, JS (GraalJS), NodeJS (GraalNodeJS), Python, Ruby, and Sulong (C/C++, Fortran).
Comparing the folder between the GraalVM SDK and say JDK 1.8.0_44 SDK, we can see that we have a handful of additional files in there:
(Use tools like meld
or just diff
to compare directories.)
Similarly, we can see that the folder has interesting differences, although it's semantically similar to the traditional Java SDKs. A few items that look interesting in the list are Rscript, lli, and ployglot.
Now we haven't literally compared the two SDKs to mark elements that are different or missing in one or the other, but the above gives us an idea about what is offered and how to use the features it provides — well, this SDK has them baked into it the examples
folder.
If the examples folder is NOT distributed in the future versions, please use the respective code snippets provided for each of the sections referred to (for each language).
For this post, you won't need the examples folder to be present.
$ tree -L 1 examples
(Use the tree
command — sudo apt-get tree
to see the above — available on MacOSX and Windows)
Each of the sub-folders contains examples for the respective languages supported by the GraalVM, including embed
and native-image
, which we will also be looking at.
Exciting Part: Hands-On Using the Examples
Let's cut to the chase. But before we can execute any code and see what the examples do, we should move graalvm-0.30
to where the other Java SDKs reside, let's say under /usr/lib/jvm/
, and set an environment variable called GRAAL_HOME
to point to it:
$ sudo mv -f graalvm-0.30 /usr/lib/jvm
$ export GRAAL_HOME=/usr/lib/jvm/graalvm-0.30
$ echo "export GRAAL_HOME=/usr/lib/jvm/graalvm-0.30" >> ~/.bashrc
$ cd examples
R Language
Let's pick R
and run some R
script files:
$ cd R
$ $GRAAL_HOME/bin/Rscript --help #to see the usage text
Beware that we are running Rscript
and not R
, both can run R scripts — the latter is an R
REPL.
Running hello_world.R
using Rscript
:
$ $GRAAL_HOME/bin/Rscript hello_world.R
[1] "Hello world!"
JavaScript
Next we try out some JavaScript
:
$ cd ../js/
$ $GRAAL_HOME/bin/js --help # to get to see the usage text
Running hello_world.js
with js
:
$ $GRAAL_HOME/bin/js hello_world.js
Hello world!
Embed
Now let's try something different — what if you wish to run code written in multiple languages, all residing in the same source file, on the JVM.
$ cd ../embed
We can do that using the org.graalvm.polyglot.context
class. Here's a snippet of code from HelloPolyglotWorld.java
:
import org.graalvm.polyglot.*;
public class HelloPolyglotWorld {
public static void main(String[] args) throws Exception {
System.out.println("Hello polyglot world Java!");
Context context = Context.create();
context.eval("js", "print('Hello polyglot world JavaScript!');");
context.eval("ruby", "puts 'Hello polyglot world Ruby!'");
context.eval("R", "print('Hello polyglot world R!');");
context.eval("python", "print('Hello polyglot world Python!');");
}
}
Compile it with the following command to get a .class
file created:
$ $GRAAL_HOME/bin/javac HelloPolyglotWorld.java
And run it with the following command to see how that works:
$ $GRAAL_HOME/bin/java HelloPolyglotWorld
Hello polyglot world Java!
Hello polyglot world JavaScript!
Hello polyglot world Ruby!
[1] "Hello polyglot world R!"
Hello polyglot world Python!
You might have noticed a bit of sluggishness with the execution when switching between languages and printing the "Hello polyglot world...." messages. Hopefully, we will learn why this happens — and maybe even be able to fix it.
Native Image
The native image feature with the GraalVM SDK helps improve the startup time of Java applications and gives them a smaller footprint. Effectively, it's converting bytecode that runs on the JVM (on any platform) to native code for a specific OS/platform — which is where the performance comes from. It's using aggressive ahead-of-time (AOT) optimizations to achieve good performance.
Let's see how that works.
$ cd ../native-image
Let's take a snippet of Java code from HelloWorld.java
in this folder:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
Compile it into bytecode:
$ $GRAAL_HOME/bin/javac HelloWorld.java
Compile the bytecode (HelloWorld.class) into native code:
$ $GRAAL_HOME/bin/native-image HelloWorld
classlist: 740.68 ms
(cap): 1,042.00 ms
setup: 1,748.77 ms
(typeflow): 3,350.82 ms
(objects): 1,258.85 ms
(features): 0.99 ms
analysis: 4,702.01 ms
universe: 288.79 ms
(parse): 741.91 ms
(inline): 634.63 ms
(compile): 6,155.80 ms
compile: 7,847.51 ms
image: 1,113.19 ms
write: 241.73 ms
[total]: 16,746.19 ms
Taking a look at the folder, we can see the Hello World source and the compiled artifacts:
3.8M -rwxrwxr-x 1 xxxxx xxxxx 3.8M Dec 12 15:48 helloworld
12K -rw-rw-r-- 1 xxxxx xxxxx 427 Dec 12 15:47 HelloWorld.class
12K -rw-rw-r-- 1 xxxxx xxxxx 127 Dec 12 13:59 HelloWorld.java
The first file, helloworld, is the native binary that runs on the platform we compiled it on using the native-image command, which can be directly executed with the help of the JVM:
$ helloworld
Hello, World!
Even though we gain performance, we might be losing out on other features that we get when running in bytecode form on the JVM — the choice of which route to take is all a matter of the use case and what is important for us.
That calls for a wrap up — it's quite a lot to read and try out on the command-line, but it's well worth the time to explore the interesting GraalVM.
To sum up, we went about downloading the GraalVM from Oracle Lab's website, unpacked it, had a look at the various folders, and compared it with our traditional looking Java SDKs. Then, we noticed and noted the differences.
We further looked at the examples provided for the various Graal-supported languages and picked up a handful of features, which gave us a taste of what the GraalVM can offer. While we can run our traditional Java applications on it, we now also have the opportunity to write applications expressed in multiple supported languages in the same source file or the same project. This also gives us the ability to do seamlessly interop between the different aspects of the application written in a different language. We also have the ability to re-compile our existing applications for native environments (native-image) for performance and a smaller footprint.
For more details on examples, please refer to http://www.graalvm.org/docs/examples/.
Published at DZone with permission of Mani Sarkar. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments