/ GRAALVM, PERFORMANCE, POLYGLOT

My first impressions about Graal VM

Last week was the release of Oracle’s GraalVM. As stated on the website:

High-performance polyglot VM

GraalVM is a universal virtual machine for running applications written in JavaScript, Python 3, Ruby, R, JVM-based languages like Java, Scala, Kotlin, and LLVM-based languages such as C and C++.

GraalVM removes the isolation between programming languages and enables interoperability in a shared runtime. It can run either standalone or in the context of OpenJDK, Node.js, Oracle Database, or MySQL.

There are several factors that could make one want to switch from a regular JRE to Graal VM:

  1. One of them could be the improved performance that it claims
  2. Another one could be the polyglot feature, to transparently mix and match supported languages
  3. The final one is a blend of the former: with native support, one could ship Java apps as native code

As a patented geek, I wanted to have first look real quick. Here are my first impressions.

Enterprise Edition or not?

The first step is to download Graal VM itself. It comes into two flavors:

Community Edition
  • All open-source license
  • Free for production use
Enterprise Edition
  • Free for evaluation and other non-production uses
  • For commercial use and support options, the sales team should be contacted

First surprise: the CE edition is only available for Linux operating systems. For OSX, one should get the EE version.

There’s no edition for Windows (yet?)

Graal VM structure

The structure is similar to the one of traditional pre-9 Java JDK.

Structure of the GraalVM JDK

Hence, GraalVM can be a drop-in replacement for any standard JDK.

Running java -version returns the following output:

java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
GraalVM 1.0.0-rc1 (build 25.71-b01-internal-jvmci-0.42, mixed mode)
As of now, GraalVM is limited to Java 8 capabilities

Some performance benchmarks

The next step was to check whether there was an improvement in performance. I used the JMH framework: it’s dedicated to that.

I used the following code:

public class MyBenchmark {

    @Benchmark
    public void testMethod() {
        List<String> randomStrings = Stream.generate(() -> RandomStringUtils
                .randomAlphabetic(25))
                .limit(100_000)
                .collect(Collectors.toList());
        randomStrings.sort(String::compareToIgnoreCase);
    }
}

It was tested on 3 different JREs using the java -jar target/benchmarks.jar command-line.

GraalVM
# JMH version: 1.20
# VM version: JDK 1.8.0_161, VM 25.71-b01-internal-jvmci-0.42
# VM invoker: /usr/local/graalvm-1.0.0-rc1/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: ch.frankel.blog.MyBenchmark.testMethod

Result "ch.frankel.blog.MyBenchmark.testMethod":
  6.795 ±(99.9%) 0.016 ops/s [Average]
  (min, avg, max) = (6.477, 6.795, 6.967), stdev = 0.068
  CI (99.9%): [6.778, 6.811] (assumes normal distribution)


# Run complete. Total time: 00:06:59

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.testMethod  thrpt  200  6.795 ± 0.016  ops/s
Oracle JDK 8
# JMH version: 1.20
# VM version: JDK 1.8.0_92, VM 25.92-b14
# VM invoker: /Library/Java/JavaVirtualMachines/jdk1.8.0_92.jdk/Contents/Home/jre/bin/java
# VM options: <none>
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: ch.frankel.blog.MyBenchmark.testMethod

Result "ch.frankel.blog.MyBenchmark.testMethod":
  6.727 ±(99.9%) 0.017 ops/s [Average]
  (min, avg, max) = (6.466, 6.727, 6.899), stdev = 0.070
  CI (99.9%): [6.710, 6.743] (assumes normal distribution)


# Run complete. Total time: 00:07:00

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.testMethod  thrpt  200  6.727 ± 0.017  ops/s
Oracle JDK 9
# JMH version: 1.20
# VM version: JDK 9.0.4, VM 9.0.4+11
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home/bin/java
# VM options: <none>
# Warmup: 20 iterations, 1 s each
# Measurement: 20 iterations, 1 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: ch.frankel.blog.MyBenchmark.testMethod

Result "ch.frankel.blog.MyBenchmark.testMethod":
  7,136 ±(99.9%) 0,026 ops/s [Average]
  (min, avg, max) = (6,464, 7,136, 7,443), stdev = 0,111
  CI (99.9%): [7,110, 7,162] (assumes normal distribution)


# Run complete. Total time: 00:07:26

Benchmark                Mode  Cnt  Score   Error  Units
MyBenchmark.testMethod  thrpt  200  7,136 ± 0,026  ops/s

Here’s the sum-up:

GraalVMOracle JDK 8Oracle JDK 9

Average ops/s

6.795 ±(99.9%) 0.016

6.727 ±(99.9%) 0.017

7,136 ±(99.9%) 0,026

Min

6.477

6.466

6,464

Max

6.967

6.899

7,443

Std dev

0.068

0.070

0,111

CI (99.9%) (assumes normal distribution)

[6.778, 6.811]

[6.710, 6.743]

[7,110, 7,162]

Numbers speak for themselves: performance-wise, the gap between Graal VM and Java 8 is not significant. There’s one between them and Java 9 though. Also, Java 9 has the highest standard deviation.

Going native

GraalVM is able to turn a JAR into a native executable, via the native-image command-line. I tried to do it with the created JAR.

native-image -H:+JNI -jar target/benchmarks.jar

However, when trying to run the newly created binary, I stumbled upon the following:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.lang.Throwable.<init>(Throwable.java:310)
	at java.lang.Exception.<init>(Exception.java:102)
	at java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:89)
	at java.lang.reflect.InvocationTargetException.<init>(InvocationTargetException.java:72)
	at com.oracle.svm.reflect.proxies.Proxy_3_Main_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Caused by: java.lang.IllegalArgumentException: class org.openjdk.jmh.runner.options.TimeValue is not a value type
	at java.lang.Throwable.<init>(Throwable.java:265)
	at java.lang.Exception.<init>(Exception.java:66)
	at java.lang.RuntimeException.<init>(RuntimeException.java:62)
	at java.lang.IllegalArgumentException.<init>(IllegalArgumentException.java:52)
	at joptsimple.internal.Reflection.findConverter(Reflection.java:66)
	at joptsimple.ArgumentAcceptingOptionSpec.ofType(ArgumentAcceptingOptionSpec.java:106)
	at org.openjdk.jmh.runner.options.CommandLineOptions.<init>(CommandLineOptions.java:109)
	at org.openjdk.jmh.Main.main(Main.java:41)
	... 4 more

Worse, trying to create an image for the Spring Pet Clinic fails with the following:

error: unsupported features in 3 methods
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
  Unsupported constructor java.lang.ClassLoader.<init>(ClassLoader) is reachable:
    The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime.
The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing java.security.SecureClassLoader.<init>(SecureClassLoader.java:76)
Call path from entry point to java.security.SecureClassLoader.<init>(ClassLoader):
	at java.security.SecureClassLoader.<init>(SecureClassLoader.java:76)
	at java.net.URLClassLoader.<init>(URLClassLoader.java:100)
	at org.springframework.boot.loader.LaunchedURLClassLoader.<init>(LaunchedURLClassLoader.java:50)
	at org.springframework.boot.loader.Launcher.createClassLoader(Launcher.java:74)
	at org.springframework.boot.loader.Launcher.createClassLoader(Launcher.java:64)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:49)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
  Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime.
  The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing java.net.URL.setURLStreamHandlerFactory(URL.java:1118)
Call path from entry point to java.net.URL.setURLStreamHandlerFactory(URLStreamHandlerFactory):
	at java.net.URL.setURLStreamHandlerFactory(URL.java:1110)
	at org.springframework.boot.loader.jar.JarFile.resetCachedUrlHandlers(JarFile.java:383)
	at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:373)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:48)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException:
  Unsupported method java.security.ProtectionDomain.getCodeSource() is reachable:
    The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime.
  The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing org.springframework.boot.loader.Launcher.createArchive(Launcher.java:118)
Call path from entry point to org.springframework.boot.loader.Launcher.createArchive():
	at org.springframework.boot.loader.Launcher.createArchive(Launcher.java:117)
	at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:38)
	at org.springframework.boot.loader.JarLauncher.<init>(JarLauncher.java:35)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;
	.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: Processing image build request failed

Dynamic class-loading just doesn’t work with GraalVM.

I tried with the H2 and HSQLDB standalone JARs…​ to no avail. In both cases, I got the following stack:

java.lang.NullPointerException
	at com.oracle.graal.pointsto.ObjectScanner.scanField(ObjectScanner.java:113)
	at com.oracle.graal.pointsto.ObjectScanner.doScan(ObjectScanner.java:263)
	at com.oracle.graal.pointsto.ObjectScanner.finish(ObjectScanner.java:307)
	at com.oracle.graal.pointsto.ObjectScanner.scanBootImageHeapRoots(ObjectScanner.java:78)
	at com.oracle.graal.pointsto.ObjectScanner.scanBootImageHeapRoots(ObjectScanner.java:60)
	at com.oracle.graal.pointsto.BigBang.checkObjectGraph(BigBang.java:581)
	at com.oracle.graal.pointsto.BigBang.finish(BigBang.java:552)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:653)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:381)
	at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)----

Conclusion

GraalVM has many promising features, including polyglot, even though I’m not much interested in that one by now. However, from my limited tire-kicking, it still has room for improvement. Performance-wise, it’s outperformed by Java 9. Also, the native image creation is too limited in its current state to be useful. IMHO, it’s in a MVP state at the moment. I’m eagerly waiting the next version, and hope they will improve on the above issues.

Nicolas Fränkel

Nicolas Fränkel

Nicolas Fränkel is a Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with narrower interests like Software Quality, Build Processes and Rich Internet Applications. Currently working for Exoscale. Also double as a teacher in universities and higher education schools, a trainer and triples as a book author.

Read More