GraalVM: the holy graal of polyglot JVM?
Transposit is built on a Java backend uses Nashorn. We wanted to support ES6 syntax, so we investigated alternatives.
GraalVM is Oracle’s recommended replacement for Nashorn, despite the technology being relatively new. We liked the idea of a more performant compiler, and we’re interested in potentially using the GraalVM to allow customers to script in other languages like Python or Ruby besides our currently supported languages (JS, SQL).
We considered introducing NodeJS into our stack, but decided it would take more time and effort than we wanted to invest just to be able to use ES6 syntax. So we chose GraalVM instead because the migration path from Nashorn is clear and fast.
As easy as advertised?
While some things were well documented (such as Nashorn compatibility mode), adopting such a new technology unsurprisingly had areas that lacked documentation and included a few hurdles to jump. We want to share with you all some lessons learned from our adventure.
GraalVM is a collection of many projects, not all of which need to be used at the same time. In the case of migrating from Nashorn to GraalVM, we used:
- Truffle: A Java library for building language implementations.
- GraalVM Polyglot API: The API that enables the embedding of Truffle languages (the guest language) within the JVM (the host language).
- Graal Compiler: A JIT Java compiler that shows impressive performance benefits generally, and specifically it provides optimized performance for Truffle-based languages running on the JVM.
One of the most popular GraalVM features is compiling JVM programs to native. This feature is great for those who need smaller executables or faster startup times, but passing Java objects to Graal languages isn’t currently supported by native. Fortunately, the performance improvements gained by compiling to native isn’t an immediate need of ours.
Running GraalJS on JDK 8, JDK 11, and beyond
<!-- https://mvnrepository.com/artifact/org.graalvm.sdk/graal-sdk --> <dependency> <groupId>org.graalvm.sdk</groupId> <artifactId>graal-sdk</artifactId> <version>1.0.0-rc8</version> </dependency> <!-- https://mvnrepository.com/artifact/com.oracle.truffle/truffle-api --> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-api</artifactId> <version>1.0.0-rc8</version> </dependency> <!-- https://mvnrepository.com/artifact/org.graalvm.js/js --> <dependency> <groupId>org.graalvm.js</groupId> <artifactId>js</artifactId> <version>1.0.0-rc8</version> </dependency>
For those on JDK 11, you can run the Graal Compiler using the following flags:
You can also use the Graal Compiler by downloading it directly. If you go this route, Truffle and GraalJS are included by default, and so you only need to explicitly add the SDK:
<dependency> <groupId>org.graalvm.sdk</groupId> <artifactId>graal-sdk</artifactId> <version>1.0.0-rc8</version> </dependency>
Reading from multiple threads
With Nashorn we’d been caching our ScriptObjectMirror objects for parallel runs of the same function. We found that GraalJS disallows reads from the same Context across threads. As a result we’ve stopped caching across threads.
Now that we no longer cache Contexts, we have to remember to close each Context once we’re done with it:
if the context is no longer needed, it is necessary to close it to ensure that all resources are freed. Contexts are also AutoCloseable for use with the Java try-with-resources statement.
Listing languages in META-INF
The Graal SDK looks for languages to use under
META-INF/truffle/language. The published GraalJS library comes with its own list of languages, but 1.0.0-rc8 and rc9 don’t include the regex language that it’s dependent upon. For the moment, we wrote our own
PolyglotBindings vs. Bindings
Most of the examples in the GraalVM documentation use
context.getPolyglotBindings(). However, polyglot bindings require an import statement in the JS, so we use
Nashorn does not provide the CommonJS
require() syntax and unfortunately neither does GraalJS. There’s a popular solution for Nashorn called nashorn-commonjs-modules. Graal tooling is less evolved, so we created our own commonjs fork for our Graal solution.
While most of the hype around GraalVM has been around compiling JVM projects to native, we found plenty of value in its Polyglot APIs.GraalVM is a compelling and already fully useable alternative to Nashorn, though the migration path is still a little rocky, mostly due to a lack of documentation. Hopefully this post helps others find their way off of Nashorn and on to the holy graal.