Faster warmup, smaller downloads, JDK 16 — GraalVM 21.1 is here!

Oleg Šelajev
graalvm
Published in
14 min readApr 20, 2021

As with every release, we’re incredibly grateful to the GraalVM community for all the feedback, collaboration, GitHub issues, pull requests, and for spreading the word about GraalVM! You make GraalVM the increasingly successful and ever-improving project it is!

GraalVM 21.1 is available for download

GraalVM 21.1 is available for download from these websites:

In this article we want to highlight some of the most important improvements in GraalVM 21.1. If you’re interested in a particular component and would like to see a detailed list of changes, please consult the 21.1 release notes.

And if your preferred way to learn is videos rather than text, here’s a live stream discussing the updates in GraalVM 21.1:

Platform Updates

Oracle GraalVM Enterprise Edition 21.1 is based on Oracle JDK version 1.8.0_291 and Oracle JDK version 11.0.11. GraalVM Community Edition in 21.1 is based on OpenJDK version 1.8.0_292 and OpenJDK version 11.0.11.

GraalVM 21.1 introduces new experimental binaries based on JDK 16 for both Enterprise and Community editions based on JDK 16.0.1.

The experimental status means that all components in the JDK 16 based binaries are considered experimental regardless what their status is in other distribution versions.

The JIT mode for Java applications is perhaps the most tested capability for these builds, so if you are interested in running your Java applications with the GraalVM compiler or are currently using the JVMCI compiler in any other JDK 16 OpenJDK builds, consider trying out the GraalVM binaries. These include the latest OpenJDK changes and the latest GraalVM compiler changes which is the best of both worlds setup.

GraalVM offers experimental JDK 16 based builds now

Java 16 is the current release of Java and we are looking forward to providing support for the upcoming Java 17 LTS release.

Note that due to the decommissioning of the aging macOS 10.7 build infrastructure, GraalVM Community Edition releases based on JDK 8 are no longer being built.

Node.js included in GraalVM 21.1 has been updated to 14.16.1, which is recommended for most users.

There’s one more significant change regarding Node in GraalVM. As of GraalVM 21.1, Node.js support is no longer included in the base GraalVM download. It’s now a separate component that you can install with the gu install nodejs command. JavaScript support continues to be a part of the base download, it's just the Node.js support that's installable separately. This change is for speed and clarity — it aims to reduce the size of the base GraalVM download, and to reduce confusion among some users who want to use GraalVM primarily as their main JDK.

GraalVM CE 21.1 download is smaller than in the previous release.

Compiler Updates

Updates to the compiler are especially exciting because they improve GraalVM across the board since the compiler underpins the performance of all the various languages supported on GraalVM!

In 21.1 there are two particularly interesting compiler improvements:

One new optimization eliminates unneeded memory barriers on sequential volatile writes on x86. Numerous volatile writes sometimes occur in a sequence after the code has been inlined. The GraalVM compiler now omits the memory barrier for all but the last write in the sequence, nicely speeding up methods like ConcurrentHashMap.transfer().

The other main improvement, which can help increase performance on similar workloads, is more of a fix rather than a whole new optimization. In the following example method, also from ConcurrentHashMap, Unsafe is used to get an Object from an array. With this fix, the GraalVM compiler eliminates the cast:

static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {      
return (Node<K,V>)U.getObjectVolatile(
tab,
((long)i << ASHIFT) + ABASE);
}

Loops Updates

This release includes support in the GraalVM Enterprise compiler for vectorizing loops that have the hashCode-like pattern. Hashcode is often computed with an idiom like: hash = c * hash + array[i] , which the compiler can now recognize and vectorize.

We also want to highlight two new GraalVM Enterprise features related to inverted loops. First, the compiler detects inverted loops as counted loops:

Here’s an example of a tail-counted, inverted:

int i = 0; 
do {
// loop body
i++;
} while (i < limit);

Note that the loop limit check is at the end of the loop body. GraalVM Enterprise now detects such loops as counted, enabling them to be analyzed and optimized like all other counted loops making them subject to full unrolling, partial unrolling, guard optimization, and vectorization. This new capability can be disabled with the following option:

-Dgraal.DetectInvertedLoopsAsCounted=false.

We have seen performance improvements of up to 40% for applications with inverted loops in hot code paths! A prominent example of frequent inverted loop usage is the LLVM bitcode runtime in GraalVM. It compiles LLVM IR, and the internal loop representation in Clang often resembles inverted loops.

The second improvement in this regard is a novel loop inversion optimization. This adds compiler support to GraalVM Enterprise to generate inverted loops from regular ones. Inverted loops have superior characteristics for instruction-level parallelism and optimization capabilities compared to regular, head counted loops.

Consider this example loop:

int limit = ...; // e.g. a parameter int i = 0; 
while (i < limit) {
array[i] += 10;
i++;
}

Every iteration of this loop has to perform an index bounds check and throw an ArrayIndexOutOfBoundsException if it is above the array length. The guard motion optimization in GraalVM Enterprise will generate an optimized version of the guard that can be hoisted before the loop:

int limit = ...; 
int i = 0;
// transfer to interpreter to raise ArrayIndexOutOfBoundsException
if (limit >= array.length || limit < 0) deoptimize();
while (i < limit) {
array[i] += 10;
i++;
}

The disadvantage of this code is that the guard is checked even if the loop is never entered. It isn’t the best use of time and resources. This could be solved by replicating the check that determines if the loop will be entered, but this is an extra work and increases code size. By inverting the loop, however, we get:

int limit = ...;
int i = 0;
if (i < limit) {
// landing pad
if (limit >= array.length || limit < 0) deoptimize();
do {
array[i] += 10;
i++;
} while (i < limit);
}

This creates a natural landing pad for guard motion and loop invariant expressions before the loop that is only executed if the original loop is executed. We have seen performance improvements of up to 30% for micro benchmarks exercising the inverted loop shape. Not too shabby!

Native Image

As in every release, there are numerous fixed issues and compatibility improvements in Native Image, along with new features.

GraalVM 21.1 adds support for multiple locales in Native Image. Now you can specify at build time which locales should be included in the generated executable. For example, to switch the default locale to German and also include French and English, use -H:DefaultLocale=de -H:IncludeLocales=fr,en. All locales can be included via -H:+IncludeAllLocales. ResourceBundles are included by default for all selected locales, but this can be changed by providing a locale-specific substring when requesting the bundle.

Further improvements — such as updating the Native Image assisted configuration agent to help with locale configurations, and loading bundles from an external storage — are planned for the next release.

We also deprecated the use of the --enable-all-security-services option. It is no longer necessary as security services are now registered automatically when usage is detected by static analysis during build time.

This release improves support for Native Image on Windows. Applications built with Native Image now do file globbing on parameters. Globbing is interpreting standard special characters like * and ? to match the filenames.

There are also other nice additions, like added reporting on the Native Image build in order to produce multiple artifacts, with results written to the <image-name>.build_artifacts.txt file. Also, multiple different Native Image compiled artifacts are can be used in the same process. For example, an application built with Native Image can use a Native Image generated shared library.

Polyglot Runtime

If you’re using any of the Truffle languages — JavaScript, Python, Ruby, R, Webassembly, Java on Truffle, TruffleSqueak, LLVM bitcode, etc. — changes to the polyglot runtime are important to your applications!

The polyglot runtime in GraalVM 21.1 has multi-tier compilation enabled by default. This feature was first introduced in 20.3 as an experimental option. With it enabled, Truffle uses two tiers of compilation for polyglot languages. The first tier is optimized for compilation speed and reuses the GraalVM compiler economy configuration to perform speedy compilations with method inlining of trivial methods only. The second tier compiles with all GraalVM’s compiler optimizations enabled and is performed after the first tier compilation has completed. Our benchmark results show that this improves warmup for most languages significantly.

In the adjacent article we look into more details about multi-tier compilation in GraalVM 21.1 and explore its impact on various benchmarks and Truffle languages.

To support flexibility of the multi-tier compilation, 21.1 also introduces new experimental flags to set the compilation thresholds: --engine.FirstTierCompilationThreshold sets the threshold for the first, and --engine.LastTierCompilationThreshold for the second tier. If multi-tier compilation is disabled, --engine.SingleTierCompilationThreshold may be used. The original existing option, --engine.CompilationThreshold, is now deprecated and will be taken out behind the barn and set free (removed) in a future release.

On top of improving warmup with the multi-tier compilation, this release also includes a new experimental compilation queue implementation inspired by HotSpot. This implementation is not yet enabled by default, but you can try it on your workloads using the --engine.TraversingCompilationQueue option. Give it a whirl. It is expected to reduce warmup time even more.

Your mileage may vary and it all heavily depends on the workload and details of your codebase, but startup and warmup performances of Truffle languages should show significant improvements over previous releases!

For Polyglot Embedding/Guest Language Users

If you’re using Truffle languages from a Java application, the changes listed here may be important for you.

One interesting new capability added in GraalVM 21.1 is an implementation of the guest languages safepoints. Use Context.safepoint() to cooperatively poll thread local actions of a polyglot context while a host method is executed. This allows to check for interruption or cancellation from within the Context.

This release also adds a new experimental sandbox option to GraalVM Enterprise to constrain memory usage of the guest language. Use the --sandbox.MaxHeapMemory=<size> option that controls the maximum heap memory that a guest application can retain during its run. This feature is currently only supported on HotSpot. More information on how to use heap memory limits can be found in the sandbox documentation.

There are three welcome additions to the Truffle polyglot support for data-structures: byte buffer-like, iterator and iterable-like, and map-like data structures! Support for these features in the individual languages may vary as they need to implement the changes as well, but in a nutshell it means the following:

  • java.nio.ByteBuffer implementations work like native guest language buffers.
  • java.util.Iterator and java.util.Iterable implementations work like guest language iterators.
  • java.util.Map implementations are handled to make them look like guest language hash maps.
  • guest language iterators and hash maps can be mapped to Java ones with Value.as(Iterator.class), Value.as(Map.class).
  • Value class API includes new methods for accessing these new data structures: Value.hasBufferElements(), Value.isIterator() and Value.hasIterator(), and Value.hasHashEntries().

Of course, these features can be controlled with the HostAccess.Builder methods allowMapAccess, allowBufferAccess, allowIteratorAccess, and allowIterableAccess. This means that if a language implementation supports it, you can pass a Java Map to the context and access its entries as if it was a native guest language map.

JavaScript

As we mentioned above in the platform updates, the version of Node.js GraalVM supports has been updated to 14.16.1. In this release, the node and npm binaries are not included in the base download, but are instead available to be installed separately.

Installing the Node.js component is done using the gu utility like for all other optional components: gu install nodejs

[opc@ol8-demo 21.1]$ gu install nodejs
Downloading: Component catalog from www.graalvm.org
Processing Component: Graal.nodejs
Downloading: Component nodejs: Graal.nodejs from github.com
Installing new component: Graal.nodejs (org.graalvm.nodejs, version 21.1.0)
...

Note that if you want to use node for running polyglot programs not in the JVM mode, which is turned on by the --jvm command line option, then you need to rebuild the polyglot shared library to include all languages you want to have access to. This can be done by running gu rebuild images or following the instructions in the gu install output. In JVM mode, all installed languages can be dynamically loaded so rebuilding is not necessary.

JavaScript in GraalVM 21.1 includes Truffle’s support for iterators, iterables, and byte buffers, which allows JavaScript iterators to be used via the Value API hasIterator(), getIterator(), hasIteratorNextElement(), getIteratorNextElement(); iterable objects from other languages to be iterated in GraalVM's JavaScript runtime, e.g., via for-of loops and vice versa; for the host ByteBuffers and buffers from other languages to be used with JavaScript typed arrays, e.g., new Uint8Array(foreignBuffer), and DataView without copying; and to access ArrayBuffers via the Value API: readBuffer*, writeBuffer*.

Support for Maps is also included so you can iterate them with for in/ofloops and new Map(hash), Array.from(hash), etc. Even better if the --js.foreign-hash-properties option is enabled (which it is by default), then foreign hash maps can also be accessed using hash[key], hash.key, {...hash}. With this support, the following example now works:

Example of using new Truffle support for hashes in Graaljs 21.1

It prints the wonderfully exciting:

[opc@ol8-demo 21.1]$ javac Main.java && java Main 
GraalVM
21.1
Map(2) {"hello" => "world", 1 => 42}; world

On top of all of these excellent improvements, this release also adds the first experimental version of the WebAssembly JavaScript Interface, which allows invoking WebAssembly programs from within JavaScript. This feature is enabled by the --js.webassembly option.

Ruby

As with every release, GraalVM 21.1 reflects a solid amount of Ruby compatibility and performance work, including more complete support of Ruby 2.7 (remaining work can be seen in the GitHub issue).

Multi-tier compilation is now enabled by default, which significantly improves warmup. Other items — checks for recursion, random number generation, some cases of using class variables, irb, and pasting code into irb — all saw improvements in this release as well.

GraalVM 21.1 also includes improved support for C extensions. Installing gems which use C extensions that link to static libraries now generally work when prepending the GraalVM LLVM toolchain to your PATH. With this release, GraalVM now defaults to the vendored libxml2 and libxslt gems when installing nokogiri, similar to CRuby, which means the corresponding system packages are no longer needed.

Also, if you’re using standalone builds of TruffleRuby, note that it is now based on JDK 11, up from JDK 8 which it used previously.

Python

One of the more significant improvements in GraalVM 21.1 for Python is the enhanced support for Java subclassing and new interop APIs for better Jython migration path. Iteration over Python types from Java, catching and re-throwing Java exceptions in Python code, as well as implementing Java abstract classes and interfaces from Python are often-requested Jython features that GraalVM now provides, making the migration easier.

Another very exciting addition is SSL support. Now you can install packages and their dependencies with pip just as with CPython:

$ graalpython -m venv env 
We're not using symlinks in a Graal Python venv
$ source env/bin/activate $ pip install pygal
Collecting pygal
Downloading pygal-2.4.0-py2.py3-none-any.whl (127 kB)
|████████████████████████████████| 127 kB 983 kB/s
Looking for Graal Python patches for pygal
Installing collected packages: pygal
Looking for Graal Python patches for pygal
Successfully installed pygal-2.4.0

There’s also a new completely native backend for Posix API, which provides better performance and improved compatibility for filesystem access.

And we wrap up the list of exciting new features in GraalVM 21.1’s Python support with the addition of multi-threading with Global Interpreter Lock (GIL). GraalPython supports multi-threading to the same extent as CPython, that is, there is only concurrency, not parallelism for most workloads.

LLVM bitcode runtime

There are, as usual, oodles of performance improvements and compatibility fixes for the LLVM runtime, but in 21.1, there are also a few new features.

Pointers to foreign “buffer-like” objects, for example, JavaScript’s ArrayBuffer, can be transparently accessed like native buffers.

GraalVM now supports loading bitcode modules with regular dlopen in addition to the Polyglot API.

The option was added to support building GraalVM or native executables with “native mode” completely removed, making --llvm.managed mode the only available option. This improves security in certain scenarios.

FastR

In this release, GraalVM’s R implementation mostly saw compatibility work.

GraalVM 21.1 upgrades FastR to R version 4.0.3, which means FastR runtime mostly compatible with R 4.0.3. It also migrated to new versions of the base and recommended packages, and implements some of the new features of R 4.0.3. See the changelog for all the details.

We also upgraded the CRAN snapshot, used by default by install.packages, to 2021-02-01. Support for new versions of some of the most popular packages on FastR is a work in progress that will improve in future releases with known issues in in dplyr 1.0.3, ggplot 3.3.3, and knitr 1.31.

WebAssembly

GraalVM 21.1 has two main improvement themes: compatibility and performance. Following that philosophy, JavaScript and WebAssembly are now integrated much better than before, now allowing JavaScript to invoke WebAssembly. Basic WASI functions are now implemented, which allows manipulating files and accessing the file system through the Truffle file-system layer… and performance results are incredible!

Besides improving the speed of the standalone GraalWasm launcher and peak performance of WebAssembly on GraalVM in general, the main highlight is the more that 10x performance improvement of the interpreter, which should be most evident in short-running programs or in code that’s invoked just a few times, not enough to trigger JIT compilation. This is not a drill. We repeat — 10x performance improvement!

Java on Truffle

The GraalVM 21.0 release was the first for Java on Truffle. Since then, we’ve concentrated on compatibility and making sure it’s useful for running typical applications in typical development scenarios.

For example, we fixed a massive slowdown when instruments are enabled, so debugging your application on Java on Truffle doesn’t feel slow anymore. It was not just a single issue, but a number of edge cases in the Java Debug Wire Protocol (JDWP) support that required fixing, and it was all worth it, speeding up applications with the debugger enabled sometimes by a ridiculous 200x!

We also added support for signal handlers, which means that shutdown hooks registered in the application now run as expected when an application stops.

Work always continues on improving enhanced HotSwap capabilities. For example, as of this release, reflective access and MethodHandles now follow method changes.

Tools

Tooling is a very important part of the GraalVM Ecosystem. In every release we’re trying to improve the developer experience by improving the tools that are part of the GraalVM distribution, including the debugger and profilers, etc.. GraalVM 21.1 is no exception.

For example, VisualVM now also works on JDK 16 and has support for running on Apple M1 chips.

We’re happy to announce the new version of the VS Code GraalVM Extensions, which includes a collection of features to make working with GraalVM, Java, and Micronaut applications easier:

  • Added unit test results visualization
VS Code visualizing results of the unit tests in the testing view
  • Improved Micronaut support: YAML support and content assistance for configuration files, and more Java code editing features
Configure Micronaut applications in VS Code with the improved content assist
  • Added new supported refactorings for Java code
  • Added support for Docker build commands for Micronaut projects

Whew!

While this is a much longer post than we typically write for a release, these are still only some of the exciting improvements in GraalVM 21.1!

Again, as always, the downloads are available on the website.

Please don’t hesitate to send us any and all feedback! There are several channels available to reach the team; pick whichever works best for you: Slack, GitHub, or Twitter! We’re always happy to hear from you!

Until next time!

— GraalVM Team

--

--