Running JVM-based Apps

GraalVM gives you enhanced performance for JVM-based applications written in languages such as Java, Scala, Groovy or Kotlin. It uses Graal, a dynamic compiler that dramatically improves efficiency and speed of applications through unique approaches to code analysis and optimization.

The Graal compiler has been made available as an experimental option in JDK 10. This version of Graal is a snapshot of the community version of Graal bundled in GraalVM. We recommend using Graal with GraalVM for ultimate performance and the latest features.

Interoperability

In addition to running JVM-based languages, you can also call any other Graal language that GraalVM supports directly from Java. See the Polyglot Reference and the Embedding documentation for more information about interoperability with other programming languages.

Compiler Performance

The Graal compiler achieves excellent performance for modern workloads such as Scala or usage of the Java Streams API. For examples, see the Java performance examples.

Graal Configuration on JVM

The options for configuring Graal on the JVM are in 2 categories.

JVMCI HotSpot options

Graal interfaces with the JVM via the Java Virtual Machine Compiler Interface (JVMCI). That is, Graal is an instantiation of a JVMCI compiler. There are a number of -XX options for configuring JVMCI specific functionality. Interesting ones include:

  • -XX:-UseJVMCICompiler: Disables use of the Graal as the top tier JIT. This is useful when wanting to compare performance of Graal against the native JIT compilers.
  • -XX:+JVMCIPrintProperties: Prints help for all defined jvmci.* and graal.* system properties.
  • -XX:-UseJVMCIClassLoader: Disables the class loader used to isolate JVMCI and Graal from application code. This is useful if you want to programmatically invoke Graal.
  • -XX:+BootstrapJVMCI: Causes Graal to be compiled before running the Java main method. By default, Graal is compiled by the C1 compiler to mitigate warmup costs. To force Graal to compile itself, add either Dgraal.CompileGraalWithC1Only=false or -XX:-TieredCompilation to the command line.
  • -XX:+EagerJVMCI: By default, Graal is only initialized upon the first top tier compilation request. Use this flag to force eager initialization which can be useful for testing.
  • -XX:JVMCIThreads=1: By the default, JVM ergonomics decide how many threads are to be used for Graal compilation. When debugging Graal in a Java debugger, it often helps to restrict Graal compilation to a single thread with this option.

Graal System Properties

In addition to the JVMCI -XX options, graal.* system properties can be used to configure Graal. A selection of interesting ones is shown below. To see the complete list, use the -XX:+JVMCIPrintProperties option.

  • ShowConfiguration: Prints various information about the compiler configuration in use. This option is best used as follows: java -XX:EagerJVMCI -Dgraal.ShowConfiguration=info -version. Since Graal is only initialized upon the first top tier JIT compilation request, without -XX:+EagerJVMCI Graal may not be initialized at all in which case -Dgraal.ShowConfiguration will be ignored. Adding -version avoids the noise of the java launcher usage message while providing useful information about the VM configuration at the same time.
  • -Dgraal.CompilationFailureAction=Silent: Suppresses any output on the console when a compilation fails due to an exception indicating an internal compiler error. The default behavior (corresponding to the value Diagnose) is to retry the compilation with extra diagnostics. Upon VM shut down, the diagnostics will be collated into a single file that can be submitted with bug reports. To get notifications of compiler errors without the extra diagnostics, set this option value to Print. To exit the VM on a compiler error, set this option value to ExitVM.
  • -Dgraal.CompilationBailoutAction=Print: This option is similar to graal.CompilationFailureAction except that it applies to compilations that are aborted due to input that the compiler decides not to compile. For example, bytecode that contains unbalanced monitors. The default value for this option is Silent.
  • -Dgraal.CompileGraalWithC1Only=false: Specifies that Graal should compile itself. By default, Graal is compiled by C1.
  • -Dgraal.GraalCompileOnly=<pattern>: Restricts compilation by Graal only to methods matching <pattern>. The pattern format is the same as that for the graal.MethodFilter option whose complete help description can be seen with -XX:+JVMCIPrintProperties.
  • -Dgraal.TraceInlining=true: Shows the inlining decision tree for each method compiled.

Graal System Properties with other GraalVM Launchers

These Graal properties above are usable with some other GraalVM launchers such as node, js and lli. The prefix for specifying the properties is slightly different. For example:

$ java -Dgraal.ShowConfiguration=info

Becomes:

$ js --jvm.Dgraal.ShowConfiguration=info

Note the -D prefix is replaced by --jvm.D.

JVM Operations Manual

Difference between running Graal in the native image vs on the JVM

When running Graal on the JVM, it goes through the same warmup phase that the rest of the Java application does. That is, it is first interpreted before its hot methods are compiled. This can translate into slightly longer times until the application reaches peak performance when compared to the native compilers in the JVM such as C1 and C2.

Currently, this is the only way Graal can be used with the JVM. We are developing a mode whereby Graal itself is compiled ahead of time into a native shared library using SubstrateVM. This will provide startup times on par with the native compilers. It will also remove any impact Graal’s use of the Java heap may have on the application as Graal will have its own heap and memory manager.

Measuring Performance

The first thing to be sure of when measuring the performance of Graal is to ensure the JVM is using Graal. In a GraalVM binary, the JVM is configured to use Graal as the top tier compiler by default. You can confirm this by adding -Dgraal.ShowConfiguration=info to the command line. It will produce a line of output similar to the one below when Graal is initialized:

Using Graal compiler configuration 'community' provided by org.graalvm.compiler.hotspot.CommunityCompilerConfigurationFactory loaded from jar:file:/Users/dsimon/graal/graal/compiler/mxbuild/dists/graal.jar!/org/graalvm/compiler/hotspot/CommunityCompilerConfigurationFactory.class

Note that Graal is only initialized on the first top tier JIT compilation request so if your application is short lived, you may not see this output.

Optimizing JVM-based applications is a science in itself. Compilation may not even be a factor in the case of poor performance as the problem may lie in any other part of the VM (I/O, garbage collection, threading etc) or in poorly written application or 3rd party library code. For this reason, it’s worth using profilers such as Java Mission Control to diagnose application behavior.

You can also compare performance against the native top tier compiler in the JVM by adding -XX:-UseJVMCICompiler to the command line.

If you observe a significant performance regression when using Graal, please open an issue on GitHub. Attaching a Java Flight Recorder log and instructions to reproduce the issue makes investigation easier and thus chances of a fix higher. Even better is if you can submit a JMH benchmark that represents the hottest parts of your application (as identified by a profiler). This allows us to very quickly pinpoint missing optimization opportunities or to offer suggestions on how to restructure the code to avoid or reduce performance bottlenecks.

Troubleshooting Graal

Like all software, Graal is not guaranteed to be bug free so it is useful to know how to diagnose and submit useful bug reports if you encounter issues.

Compilation Exceptions

One advantage of Graal being written in Java is that runtime exceptions during compilation are not fatal VM errors. Instead, each compilation has an exception handler that takes an action based on the graal.CompilationFailureAction property.

The default value is Diagnose and causes failing compilations to be retried with extra diagnostics enabled. Just before the VM exits, all diagnostic output captured during retried compilations is written to a .zip file and output such as the following is printed to the console:

Graal diagnostic output saved in /Users/demo/dumps/1499768882600/graal_diagnostics_64565.zip

You can then attach the .zip file to an issue on GitHub.

You can also set the following values for the graal.CompilationFailureAction property:

  • Silent: prints nothing to the console.
  • Print: prints a message to the console but does not do re-compilation.
  • ExitVM: does the same thing as Diagnose but the VM process exits after re-compilation.

Code Generation Errors

The other type of error compilers can have is producing incorrect machine code. This error can cause a VM crash, which should produce a file that starts with hs_err_pid in the current working directory of the VM process. In most cases, there is a section in the file that shows the stack at the time of the crash, including the type of code for each frame in the stack, as in the following example:

Stack: [0x00007000020b1000,0x00007000021b1000],  sp=0x00007000021af7a0,  free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
J 761 JVMCI org.graalvm.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V (299 bytes) @ 0x0000000108a2fc01 [0x0000000108a2fac0+0x141] (null)
j  org.graalvm.compiler.core.gen.NodeLIRBuilder.doBlock(Lorg/graalvm/compiler/nodes/cfg/Block;Lorg/graalvm/compiler/nodes/StructuredGraph;Lorg/graalvm/compiler/core/common/cfg/BlockMap;)V+211
j  org.graalvm.compiler.core.LIRGenerationPhase.emitBlock(Lorg/graalvm/compiler/nodes/spi/NodeLIRBuilderTool;Lorg/graalvm/compiler/lir/gen/LIRGenerationResult;Lorg/graalvm/compiler/nodes/cfg/Block;Lorg/graalvm/compiler/nodes/StructuredGraph;Lorg/graalvm/compiler/core/common/cfg/BlockMap;)V+65

This example shows that the top frame was compiled (J) by the JVMCI compiler, which is the Graal compiler. The crash occurred at offset 0x141 in the machine code produced for:

org.graalvm.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V

The next two frames in the stack were executing in the interpreter (j). The location of the crash is also often indicated near the top of the file with something like this:

# Problematic frame:
# J 761 JVMCI org.graalvm.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V (299 bytes) @ 0x0000000108a2fc01 [0x0000000108a2fac0+0x141] (null)

In this example, there is likely an error in the code produced by Graal for NodeLIRBuilder.matchComplexExpressions.

When filing an issue on GitHub for such a crash, you should first attempt to reproduce the crash with extra diagnostics enabled for the compilation of the problematic method. In this example, you would add the following to your command line:

-Dgraal.MethodFilter=NodeLIRBuilder.matchComplexExpressions, -Dgraal.Dump=:2

These options are described in more detail here. In brief, these options tell Graal to capture snapshots of the compiler state at verbosity level 2 while compiling any method named matchComplexExpressions in a class with a simple name of NodeLIRBuilder. The complete format of the MethodFilter option is described in the output of java -XX:+JVMCIPrintProperties.

Quite often, the crash location does not exist directly in the problematic method mentioned in the crash log but comes from an inlined method.

In such a case, simply filtering for the problematic method might not capture an erroneous compilation causing a crash.

To improve the likelihood of capturing an erroneous compilation, you need to broaden the MethodFilter value. To guide this, add -Dgraal.PrintCompilation=true when trying to reproduce the crash so you can see what was compiled just before the crash.

The following shows sample output from the console:

HotSpotCompilation-1218        Lorg/graalvm/compiler/core/amd64/AMD64NodeLIRBuilder;                  peephole                                      (Lorg/graalvm/compiler/nodes/ValueNode;)Z           |   87ms   428B   447B  1834kB
HotSpotCompilation-1212        Lorg/graalvm/compiler/lir/LIRInstructionClass;                         forEachState                                  (Lorg/graalvm/compiler/lir/LIRInstruction;Lorg/graalvm/compiler/lir/InstructionValueProcedure;)V  |  359ms    92B   309B  6609kB
HotSpotCompilation-1221        Lorg/graalvm/compiler/hotspot/amd64/AMD64HotSpotLIRGenerator;          getResult                                     ()Lorg/graalvm/compiler/hotspot/HotSpotLIRGenerationResult;  |   54ms    18B   142B  1025kB
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x000000010a6cafb1, pid=89745, tid=0x0000000000004b03
#
# JRE version: OpenJDK Runtime Environment (8.0_121-b13) (build 1.8.0_121-graalvm-olabs-b13)
# Java VM: OpenJDK 64-Bit GraalVM (25.71-b01-internal-jvmci-0.30 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# J 1221 JVMCI org.graalvm.compiler.hotspot.amd64.AMD64HotSpotLIRGenerator.getResult()Lorg/graalvm/compiler/hotspot/HotSpotLIRGenerationResult; (18 bytes) @ 0x000000010a6cafb1 [0x000000010a6caf60+0x51] (null)
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again

Here we see that the crash happened in a different method than the first crash. As such, we expand the filter argument to be -Dgraal.MethodFilter= NodeLIRBuilder.matchComplexExpressions,AMD64HotSpotLIRGenerator.getResult and run again.

When the VM crashes in this way, it does not execute the shutdown code that archives the Graal diagnostic output or delete the directory it was written to. This must be done manually after the crash.

By default, the directory is $PWD/dumps/<timestamp>; for example, ./dumps/1499938817387. However, you can set the directory with -Dgraal.DumpPath=<path>.

A message, such as the following, is printed to the console when this directory is first used by the Graal compiler:

Dumping debug output in /Users/demo/dumps/1499768882600

This directory should contain content related to the crashing method, such as:

$ ls -l /Users/demo/dumps/1499768882600
-rw-r--r--  1 demo  staff    144384 Jul 13 11:46 HotSpotCompilation-1162[AMD64HotSpotLIRGenerator.getResult()].bgv
-rw-r--r--  1 demo  staff     96925 Jul 13 11:46 HotSpotCompilation-1162[AMD64HotSpotLIRGenerator.getResult()].cfg
-rw-r--r--  1 demo  staff  12600725 Jul 13 11:46 HotSpotCompilation-791[NodeLIRBuilder.matchComplexExpressions(List)].bgv
-rw-r--r--  1 demo  staff   1727409 Jul 13 11:46 HotSpotCompilation-791[NodeLIRBuilder.matchComplexExpressions(List)].cfg

You should attach a zip of this directory to an issue on GitHub.