One of the most amazing projects I’ve learned about this year is GraalVM.

I’ve learned about this project during Devoxx Poland (a Polish developer conference) at a talk by Oleg Šelajev. If you’re curious about everything GraalVM has to offer, not just the native Java compilation, please watch his video.

GraalVM is a universal/polyglot virtual machine. This means GraalVM can run programs written in:

Javascript

Ruby

Python 3

R

JVM-based languages (such as Java , Scala, Kotlin)

, Scala, Kotlin) LLVM-based languages (such as C, C++).

In short: Graal is very powerful.

There is also the possibility to mix-and-match languages using Graal, do you want to make a nice graph in R from your Java code? No problem. Do you want to call some fast C code from Python, go ahead.

Installing GraalVM

In this blogpost though we’ll look at another powerful thing Graal can do: native-image compilation

Instead of explaining what it is, let’s just go ahead, install GraalVM and try it out.

To install GraalVM, download and unpack, update PATH parameters and you’re ready to go. When you look in the /bin directory of Graal you’ll see the following programs:

Here we recognise some usual commands, such as ‘javac’ and ‘java’. And if everything is setup correctly you’ll see:

$ java -version openjdk version "1.8.0_172" OpenJDK Runtime Environment (build 1.8.0_172-20180626105433.graaluser.jdk8u-src-tar-g-b11) GraalVM 1.0.0-rc6 (build 25.71-b01-internal-jvmci-0.48, mixed mode)

Hello World with native-image

Next up, let’s create a “Hello World” application in Java:

public class HelloWorld { public static void main ( String ... args ) { System . out . println ( "Hello World" ); } }

And just like your normal JDK, we can compile and run this code in the Graal virtual machine:

$ javac HelloWorld.java $ java HelloWorld Hello World

But the real power of Graal becomes clear when we use a third command: native-image

This command takes your Java class(es) and turns them into an actual program, a standalone binary executable, without any virtual machine! The commands you pass to native-image very similar to what you would pass to java . In this case we have the classpath and the Main class:

$ native-image -cp . HelloWorld Build on Server(pid: 63941, port: 60051)* [helloworld:63941] classlist: 1,236.06 ms [helloworld:63941] (cap): 1,885.61 ms [helloworld:63941] setup: 2,758.47 ms [helloworld:63941] (typeflow): 3,031.39 ms [helloworld:63941] (objects): 2,136.63 ms [helloworld:63941] (features): 46.04 ms [helloworld:63941] analysis: 5,304.17 ms [helloworld:63941] universe: 205.46 ms [helloworld:63941] (parse): 640.12 ms [helloworld:63941] (inline): 1,155.06 ms [helloworld:63941] (compile): 3,436.76 ms [helloworld:63941] compile: 5,594.76 ms [helloworld:63941] image: 749.82 ms [helloworld:63941] write: 653.29 ms [helloworld:63941] [total]: 16,753.87 ms $ ls -ltr -rw-r--r-- 1 royvanrijn wheel 119 Sep 20 09:36 HelloWorld.java -rw-r--r-- 1 royvanrijn wheel 425 Sep 20 09:38 HelloWorld.class -rwxr-xr-x 1 royvanrijn wheel 5596400 Sep 20 09:41 helloworld $ ./helloworld Hello World

Now we have an executable that prints “Hello World”, without any JVM in between, just 5.6mb. Sure, for this example 5mb isn’t that small, but it is much smaller than having to package and install an entire JVM (400+mb)!

Docker and native-image

So what else can we do? Well, because the resulting program is a binary, we can put it into a Docker image without ANY overhead. To do this we’ll need two different Dockerfile’s, the first is used to compile the program against Linux (instead of MacOS or Windows), the second image is the ‘host’ Dockerfile, used to host our program.

Here is the first Dockerfile:

FROM ubuntu RUN apt-get update && \ apt-get -y install gcc libc6-dev zlib1g-dev curl bash && \ rm -rf /var/lib/apt/lists/* # Latest version of GraalVM (at the time of writing) ENV GRAAL_VERSION 1.0.0-rc6 ENV GRAAL_FILENAME graalvm-ce-${GRAAL_VERSION}-linux-amd64.tar.gz # Download GraalVM RUN curl -4 -L https://github.com/oracle/graal/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} -o /tmp/${GRAAL_FILENAME} # Untar and move the files we need: RUN tar -zxvf /tmp/${GRAAL_FILENAME} -C /tmp \ && mv /tmp/graalvm-ce-${GRAAL_VERSION} /usr/lib/graalvm RUN rm -rf /tmp/* # Create a volume to which we can mount to build: VOLUME /project WORKDIR /project # And finally, run native-image ENTRYPOINT ["/usr/lib/graalvm/bin/native-image"]

This image can be created as follows:

$ docker build -t royvanrijn/graal-native-image:latest .

Using this image we can create a different kind of executable. Let’s create our application using the just created docker image:

$ docker run -it \ -v /Projects/graal-example/helloworld/:/project --rm \ royvanrijn/graal-native-image:latest \ --static -cp . HelloWorld -H:Name=app Build on Server(pid: 11, port: 40905)* [app:11] classlist: 3,244.85 ms [app:11] (cap): 1,023.94 ms [app:11] setup: 1,986.81 ms [app:11] (typeflow): 4,285.18 ms [app:11] (objects): 2,008.19 ms [app:11] (features): 57.07 ms [app:11] analysis: 6,446.49 ms [app:11] universe: 255.45 ms [app:11] (parse): 926.85 ms [app:11] (inline): 1,496.69 ms [app:11] (compile): 4,953.85 ms [app:11] compile: 7,689.47 ms [app:11] image: 806.53 ms [app:11] write: 573.77 ms [app:11] [total]: 21,160.90 ms $ ls -ltr app -rwxr-xr-x 1 royvanrijn wheel 6766144 Sep 20 10:11 app $ ./app -bash: ./app: cannot execute binary file

This results in an executable ‘app’, but this is one I can’t start on my MacBook, because it is a statically linked Ubuntu executable. So what do all these commands mean? We’ll let’s break it down:

The first part is just running Docker: docker run -it Next we map my directory containing the class files to the volume /project in the Docker image: -v /Projects/graal-example/helloworld/:/project --rm This is the Docker image we want to run, the one we just created: royvanrijn/graal-native-image:latest And finally we have the commands we pass to native-image inside the Docker image We start with --static, this causes the created binary to be a statically linked executable --static We have the class path and Main class: -cp . HelloWorld And finally we tell native-image to name the resulting executable 'app' -H:Name=app

But we can do something cool with it using the following, surprisingly empty, Dockerfile:

FROM scratch COPY app /app CMD ["/app"]

We start with the most empty Docker image you can have, scratch and we copy in our app executable and finally we run it. Now we can build our helloworld image:

$ docker build -t royvanrijn/graal-helloworld:latest . Sending build context to Docker daemon 34.11MB Step 1/3 : FROM scratch ---> Step 2/3 : COPY app /app ---> f0894b299e8f Removing intermediate container 37182de1ef68 ---> 49ff43413c7a Step 3/3 : CMD ["/app"] ---> Running in ea69a913d243 Removing intermediate container ea69a913d243 ---> ab33b4d59de3 Successfully built ab33b4d59de3 Successfully tagged royvanrijn/graal-helloworld:latest $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE royvanrijn/graal-helloworld latest ab33b4d59de3 5 seconds ago 6.77MB

We’ve now turned our Java application into a very small Docker image with a size of just 6.77MB!

In the next blogpost Part 2 we’ll take a look at Java applications larger than just HelloWorld. How will GraalVM’s native-image handle those applications, and what are the limitations we’ll run into?