【阅读文摘】GraalVM : 一个高性能的、可嵌入的、多语言的虚拟机

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/universsky2015/article/details/101398957

GraalVM is a high-performance, embeddable, polyglot Virtual Machine for running applications written in JavaScript, Python, Ruby, R, JVM-based languages like Java, Scala, Kotlin, and LLVM-based languages such as C and C++.

Additionally, GraalVM allows efficient interoperability between programming languages and compiling Java applications ahead-of-time into native executables for faster startup time and lower memory overhead.

GraalVM是一个高性能的、可嵌入的、多语言的虚拟机,用于运行用JavaScript、Python、Ruby、R、基于 JVM 的语言(如Java、Scala、Kotlin)和基于LLVM 的语言(如C和C++)编写的应用程序。

此外,GraalVM允许编程语言之间的高效互操作性,以及提前将Java应用程序编译为本机可执行程序,从而加快启动时间和降低内存开销。

GraalVM消除了编程语言之间的隔离,并支持共享运行时中的互操作性。它可以独立运行,也可以在OpenJDK、Node.js或Oracle数据库上下文中运行。

Polyglot

Zero overhead interoperability between programming languages allows you to write polyglot applications and select the best language for your task.

Native

Native images compiled with GraalVM ahead-of-time improve the startup time and reduce the memory footprint of JVM-based applications.

GraalVM can be embedded in both managed and native applications. There are existing integrations into OpenJDK, Node.js and Oracle Database.

https://github.com/oracle/graal/releases

This download includes:

JVM
JavaScript Engine & Node.js Runtime
LLVM Engine
Developer Tools, including technology preview of VSCode extensions.
The Native Image, Ruby, R, Python, and llvm-toolchain plugins are optionally available using the GraalVM gu utility.
The release notes can be found on the graalvm.org website.

Get started with GraalVM and configure it to run programs written in Java (or other JVM-based languages), JavaScript and Node.js, Ruby, R, or Python. This page guides you through downloading and installing GraalVM and adding support for additional languages, and shows you how to run simple programs on GraalVM.

Install GraalVM

GraalVM is a standalone Java Development Kit to execute Java or JVM-based languages (e.g. Scala, Kotlin), dynamic languages (e.g. JavaScript, R, Ruby, R, Python), LLVM-based languages (e.g. C and C++) in one shared runtime, and supports Linux and macOS platforms on x86 64-bit systems. It is available as Community Edition (CE) and Enterprise Edition (EE). GraalVM Community Edition is based on the OpenJDK 8. GraalVM Enterprise Edition is developed on top of the Java SE 1.8.0_221. Proceed to the Downloads page and make your choice depending on your operating system and use case.

In this getting started guide we will focus on working with GraalVM Enterprise Edition, obtained from the Oracle Technology Network. The base installation includes the JVM, the GraalVM compiler, the LLVM bitcode interpreter, and the JavaScript runtime with Node.js support – all in one package. GraalVM environment can be extended with:

  • GraalVM Native Image – a technology to compile an application ahead-of-time into a binary that runs natively on the system. Warning: GraalVM Native Image is available as an Early Adopter technology, meaning it can be used in production and will be supported by Oracle as part of the Oracle GraalVM Enterprise Edition subscription, but is not covered by Oracle’s standard warranty.
  • R, Ruby and Python languages interpreters. Warning: The support for Ruby, R and Python languages is experimental. Experimental features might never be included in a production version, or might change significantly before being considered production-ready.

Getting GraalVM installed and ready-to-go should only take a few minutes. Please note, unlike Java HotSpot VM or OpenJDK distributions for macOS that come as a .dmg file, GraalVM does not provide the installation wizard.

  1. Navigate to Oracle Technology Network Downloads page and accept the license agreement.
  2. Select and download Oracle GraalVM Enterprise Edition based on JDK8 for macOS (19.2.0.1).
  3. Extract the archive to your file system. To extract the file to the current directory from the console, type: $ tar -xvf archive.tar.gz
  4. There can be multiple JDKs installed on the macOS system and the final step is to configure the runtime environment.
    • Add the GraalVM bin folder to the PATH environment variable: $ export PATH=<path to GraalVM>/Contents/Home/bin:$PATH. Verify whether you are using GraalVM with the echo command: $ echo $PATH.
    • Set the JAVA_HOME environment variable to resolve to the GraalVM installation directory: $ export JAVA_HOME=<path to GraalVM>/Contents/Home.
  5. You can also specify GraalVM as the JRE or JDK installation in your Java IDE.

Optionally set the JAVA_HOME environment variable to resolve to the GraalVM installation directory. You can also specify GraalVM as the JRE or JDK installation in your Java IDE.

Note on macOS java_home command

The information property file, Info.plist, is in the top level Contents folder. This means that GraalVM participates in the macOS specific /usr/libexec/java_home mechanism. Depending on other JDK 8 installation(s) available, it is now possible that /usr/libexec/java_home -v1.8 returns /Library/Java/JavaVirtualMachines/graalvm-ee-19.2.0.1/Contents/Home. You can run /usr/libexec/java_home -v1.8 -V to see the complete list of 1.8 JVMs available to the java_home command. This command appears to sort the JVMs in decreasing version order and chooses the top one as the default for the specified version. Within a specific version, the sort order appears to be stable but is unspecified.

Since the executables of all language runtimes in GraalVM emulate the behavior of the languages’ default runtimes, setting GraalVM on your PATH should be enough to run an application with GraalVM.

GraalVM’s /bin directory is similar to that of a standard JDK, but includes a set of additional launchers:

  • js runs a JavaScript console with GraalVM.
  • node is a drop-in replacement for Node.js, using GraalVM’s JavaScript engine.
  • lli is a high-performance LLVM bitcode interpreter integrated with GraalVM.
  • gu (GraalVM Updater) can be used to install language packs for Python, R, and Ruby.

Notably, java runs the JVM with GraalVM’s default compiler. The Ruby, Python and R executables become available only if you install the corresponding language engines. For example, running the following command will install Ruby support:

gu install ruby

For more information on using GraalVM Updater please refer to its documentation.

Once the PATH environment variable is set properly, it is easy to check language versions with GraalVM launchers:

$ java -version
$ java -version
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit GraalVM EE 19.2.0 (build 25.221-b11-jvmci-19.2-b02, mixed mode)

$ node -v
v10.16.3

$ lli --version
LLVM (GraalVM EE Native 19.2.0.1)

If you have, e.g., the Ruby language component installed, you can check its version as well:

$ ruby -v
truffleruby 19.2.0.1, like ruby 2.6.2, GraalVM CE Native [x86_64-darwin]

Docker Containers

The official Docker images for GraalVM CE are available from the Docker Hub: https://hub.docker.com/r/oracle/graalvm-ce/.

If you want to use the Docker container with GraalVM CE, use the docker pull command:

docker pull oracle/graalvm-ce:19.2.0.1

The image is based on Oracle Linux and has GraalVM CE downloaded, unzipped and made available. It means that Java, JavaScript, Node and the LLVM interpreter are available out of the box.

You can start a container and enter the bash session with the following run command:

docker run -it oracle/graalvm-ce:19.2.0.1 bash

Check that javajs and other commands work as expected.

→ docker run -it oracle/graalvm-ce:19.2.0.1 bash
bash-4.2# java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-20190711112007.graal.jdk8u-src-tar-gz-b08)
OpenJDK 64-Bit GraalVM CE 19.2.0.1 (build 25.222-b08-jvmci-19.2-b02, mixed mode)
bash-4.2# node
> 1 + 1
2
> process.exit()
bash-4.2# lli --version
LLVM (GraalVM CE Native 19.2.0.1)
bash-4.2#

Please note that the image contains only the components immediately available in the GraalVM CE distribution. However, the GraalVM Updater utility is on the PATH. You can install the support for additional languages like Ruby, R, or Python at will. For example, the following command installs the Ruby support (the output below is truncated for brevity):

docker run -it oracle/graalvm-ce:19.2.0.1 bash
bash-4.2# gu install ruby
Downloading: Component catalog
Processing component archive: Component ruby
Downloading: Component ruby
[######              ]
...

If you want to mount a directory from the host system to have it locally available in the container, use Docker volumes.

Here is a sample command that maps the /absolute/path/to/dir/no/trailing/slash directory from the host system to the /path/inside/container inside the container.

docker run -it -v /absolute/path/to/dir/no/trailing/slash:/path/inside/container oracle/graalvm-ce:19.2.0.1 bash

If you want to create docker images that contain GraalVM Ruby, R, or Python implementation, you can use dockerfiles like the example below, which uses oracle/graalvm-ce:19.2.0.1 as the base image, installs Ruby support using the gu utility, then creates and runs a sample Ruby program.

FROM oracle/graalvm-ce:19.2.0.1
RUN gu install ruby
WORKDIR /workdir
RUN echo 'puts "Hello from Truffleruby!\nVersion: #{RUBY_DESCRIPTION}"' > app.rb
CMD ruby app.rb

If you put the above snippet in the Dockerfile in the current directory, you can build and run it with the following commands.

docker build -t truffleruby-demo .
...
$ docker run -it --rm truffleruby-demo
Hello from Truffleruby!
Version: truffleruby 19.2.0.1, like ruby 2.6.2, GraalVM CE Native [x86_64-darwin]

Running Applications

Since the executables of all language runtimes in GraalVM emulate the behavior of the languages’ default runtimes, putting GraalVM on your PATH should be enough to run your applications with GraalVM.

Running Java

Take a look at this typical HelloWorld class:

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

Run the following command to compile this class to bytecode and then run it on GraalVM:

$ javac HelloWorld.java
$ java HelloWorld
Hello World!

You can find a collection of larger examples of Java applications you can try running with GraalVM on the examples page.

Running JavaScript

GraalVM can execute plain JavaScript code, both in REPL mode and by executing script files directly.

$ js
> 1 + 2
3

GraalVM also supports running Node.js applications. More than 95,000 npm modules are tested and are fully compatible with GraalVM, including modules like express, react, async, request, browserify, grunt, mocha, and underscore. To install a Node.js module, use the npm executable in the /bin folder of the GraalVM package. The npm command is equivalent to the default Node.js command and supports all Node.js APIs.

1. Install the colors and ansispan modules using npm install:

npm install colors ansispan

After the modules are installed, you can use them from your application.

2. Add the following code snippet to a file named app.js and save it in the same directory where you installed the Node.js modules:

const http = require("http");
const span = require("ansispan");
require("colors");

http.createServer(function (request, response) {
    response.writeHead(200, {"Content-Type": "text/html"});
    response.end(span("Hello Graal.js!".green));
}).listen(8000, function() { console.log("Graal.js server running at http://127.0.0.1:8000/".red); });

3. Execute it on GraalVM using the node command:

node app.js

More information on compatibility with the Node.js and configuring GraalVM read the reference manual on JavaScript in GraalVM.

Running LLVM Interpreter

The LLVM interpreter in GraalVM executes LLVM bitcode, and therefore any programming language that can be compiled to LLVM bitcode.

Put this C HelloWorld into a file named hello.c:

#include <stdio.h>

int main() {
    printf("Hello from GraalVM!\n");
    return 0;
}

You can compile hello.c to an LLVM bitcode file named hello.bc using the following command:

$ clang -c -O1 -emit-llvm hello.c

You can then run hello.bc on GraalVM like this:

$ lli hello.bc
Hello from GraalVM!

More examples and information on what languages and runtimes are supported can be found in the reference manual for LLVM.

Running Ruby

The Ruby engine is not installed by default, but it can easily be added using GraalVM Updater:

gu install ruby

This makes Ruby commands like rubygemirbrakerdoc and ri available:

$ ruby [options] program.rb

GraalVM Ruby implementation uses the same options as the standard implementation of Ruby, with some additions.

$ gem install chunky_png
$ ruby -r chunky_png -e "puts ChunkyPNG::Color.to_hex(ChunkyPNG::Color('mintcream @ 0.5'))"
#f5fffa80

Using Bundler

GraalVM implementation of Ruby ships with the Bundler environment. Therefore its installation, gem install bundler, is not needed.

$ bundle exec ...

More examples and additional information on Ruby support in GraalVM can be found in the reference manual for Ruby.

Running R

The R engine is not installed by default, but it can easily be added using GraalVM Updater:

gu install R

When the R engine is installed, you can execute R scripts and use the R REPL with GraalVM:

$ R
R version 3.5.1 (FastR)
...

> 1 + 1
[1] 2

More examples and additional information on R support in GraalVM can be found in the reference manual for R.

Running Python

GraalVM implementation of Python 3.7 has recently been started. The Python engine is not installed by default, but it can easily be added using GraalVM Updater:

gu install python

Once the Python engine is installed, GraalVM can execute Python programs:

$ graalpython
...
>>> 1 + 2
3
>>> exit()

More examples and additional information on Python support in GraalVM can be found in the reference manual for Python.

Combine Languages

If enabled, using the --polyglot flag, scripts executed on GraalVM can use interoperability features to call into other languages and exchange data with them.

For example, running js --jvm --polyglot example.js executes example.js in a polyglot context. If the program calls any code in other supported languages, GraalVM executes that code in the same runtime as the example.js application. For more information on polyglot applications see the polyglot documentation.

Native Images

GraalVM can compile Java bytecode into native images to achieve faster startup and smaller footprint for your applications. The Native Image functionality is not available by default, but it can easily be added using gu install native-image command. Let’s use the HelloWorld example from above to demonstrate how to compile Java bytecode into a native image:

// HelloWorld.java
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, World!");
  }
}

Run the following to compile the class to bytecode and then build a native image:

$ javac HelloWorld.java
$ native-image HelloWorld

This builds an executable file named helloworld in the current working directory. Invoking it executes the natively compiled code of the HelloWorld class as follows:

$ ./helloworld
Hello, World!

Polyglot Capabilities of Native Images

GraalVM Native Image Generator also makes it easy to use polyglot capabilities. Take this example of a JSON pretty-printer using the GraalVM implementation of JavaScript:

// PrettyPrintJSON.java
import java.io.*;
import java.util.stream.*;
import org.graalvm.polyglot.*;

public class PrettyPrintJSON {
  public static void main(String[] args) throws java.io.IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    String input = reader.lines().collect(Collectors.joining(System.lineSeparator()));
    try (Context context = Context.create("js")) {
      Value parse = context.eval("js", "JSON.parse");
      Value stringify = context.eval("js", "JSON.stringify");
      Value result = stringify.execute(parse.execute(input), null, 2);
      System.out.println(result.asString());
    }
  }
}

The --language:js argument ensures that the JavaScript engine is available in the generated image:

$ javac PrettyPrintJSON.java
$ native-image --language:js --initialize-at-build-time PrettyPrintJSON

The native image generation will take several minutes as it does not just build the PrettyPrintJSON class, but includes building a JavaScript engine (along with the support for partial evaluation). Additionally, the image building requires large amounts of physical memory, especially if you build an image with Truffle Language Implementation Framework included, which is exactly the case here. Memory requirements and some other limitations of ahead-of-time compilation with GraalVM are listed here.

The resulting executable can now perform JSON pretty printing:

$ ./prettyprintjson <<EOF
{"GraalVM":{"description":"Language Abstraction Platform","supports":["combining languages","embedding languages","creating native images"],"languages": ["Java","JavaScript","Node.js", "Python", "Ruby","R","LLVM"]}}
EOF

Here is the JSON output from the native executable:

{
  "GraalVM": {
    "description": "Language Abstraction Platform",
    "supports": [
      "combining languages",
      "embedding languages",
      "creating native images"
    ],
    "languages": [
      "Java",
      "JavaScript",
      "Node.js",
      "Python",
      "Ruby",
      "R",
      "LLVM"
    ]
  }
}

The native image is much faster than running the same code on the JVM directly:

$ time bin/java PrettyPrintJSON < test.json > /dev/null
real	0m1.101s
user	0m2.471s
sys	0m0.237s

$ time ./prettyprintjson < test.json > /dev/null
real	0m0.037s
user	0m0.015s
sys	0m0.016s

If you want to learn what GraalVM offers to different types of teams, read the Why GraalVM page. Some of the diverse features of GraalVM are disclosed and supported with examples in Top 10 Things To Do With GraalVM article. Or, you can examine different supported languages in action by looking at example applications. If you want to learn about the common tools GraalVM enables for the supported languages, proceed to the tools section of the reference manual. And if you are mostly interested in a specific language, more extensive documentation is available in the reference manual as well.

Running JVM-based Apps

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

Interoperability

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

Compiler Performance

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

Compiler Operating Modes

There are two operating modes of the GraalVM compiler when used as a HotSpot JIT compiler:

  • libgraal: the GraalVM compiler is compiled ahead of time into a native shared library. In this operating mode, the shared library is loaded by the HotSpot VM. The compiler uses memory separate from the HotSpot heap and it runs fast from the start since it does not need to warm-up. This is the default and recommended mode of operation.

  • jargraal: the GraalVM compiler goes through the same warm-up phase that the rest of Java application does. That is, it is first interpreted before its hot methods are compiled. This mode is selected with the -XX:-UseJVMCINativeLibrary command line option. This will delay the time to reach peak performance as the compiler itself needs to be compiled before it produces code quickly. This mode allows you to debug the GraalVM compiler with a Java debugger.

Compiler Configuration on JVM

The options for configuring the GraalVM compiler on the JVM are in 3 categories.

General options

These are general options for setting/getting configuration details.

  • -XX:-UseJVMCICompiler: This disables use of the GraalVM compiler as the top tier JIT. This is useful when wanting to compare performance of the GraalVM compiler against the native JIT compilers.
  • -Dgraal.CompilerConfiguration=<name>: Selects the GraalVM compiler configuration to use. If omitted, the compiler configuration with the highest auto-selection priority is used. To see the set of available configurations, supply the value help to this option.

    The current configurations and their semantics are:

    • enterprise: To produce highly optimized code with a possible trade-off to compilation time. This value is only available in GraalVM EE.
    • community: To produce reasonably optimized code with a faster compilation time.
    • economy: To compile as fast as possible with less optimal throughput of the generated code.
  • -Dgraal.ShowConfiguration=none: Prints information about the GraalVM compiler configuration selected. This option only produces output when the compiler is initialized. By default, the GraalVM compiler is initialized on the first top-tier compilation. For this reason, the way to use this option is as follows: java -XX:+EagerJVMCI -Dgraal.ShowConfiguration=info -version.

    The accepted values for this option are:

    • none: To show no information.
    • info: To print one line of output showing the name of the compiler configuration in use and the location it is loaded from.
    • verbose: To print detailed compiler configuration information.
  • -Dgraal.MitigateSpeculativeExecutionAttacks=None: Selects a strategy to mitigate speculative execution attacks (e.g., SPECTRE).

    Accepted values are:

    • None: No mitigations are used in JIT compiled code.
    • AllTargets: All branches are protected against speculative attacks. This has a large performance impact.
    • GuardTargets: Only branches that preserve Java memory safety are protected. This has reduced performance impact.
    • NonDeoptGuardTargets: Same as GuardTargets except that branches which deoptimize are not protected since they can not be executed repeatedly.

Performance tuning options

  • -Dgraal.UsePriorityInlining=true: This can be used to disable use of the advanced inlining algorithm that favors throughput over compilation speed. This option is only available in GraalVM EE.
  • -Dgraal.Vectorization=true: This can be used to disable the auto vectorization optimization. This option is only available in GraalVM EE.
  • -Dgraal.OptDuplication=true: This can be used to disable the path duplication optimizationThis option is only available in GraalVM EE.
  • -Dgraal.TraceInlining=false: Enables tracing of inlining decisions. This can be used for advanced tuning where it may be possible to change the source code of the program. The output format is shown below:

    compilation of 'Signature of the compilation root method':
    at 'Sig of the root method' ['Bytecode index']: <'Phase'> 'Child method signature': 'Decision made about this callsite'
      at 'Signature of the child method' ['Bytecode index']:
         |--<'Phase 1'> 'Grandchild method signature': 'First decision made about this callsite'
         \--<'Phase 2'> 'Grandchild method signature': 'Second decision made about this callsite'
      at 'Signature of the child method' ['Bytecode index']: <'Phase'> 'Another grandchild method signature': 'The only decision made about this callsite.'
    

    For example:

    compilation of java.lang.Character.toUpperCaseEx(int):
    at java.lang.Character.toUpperCaseEx(Character.java:7138) [bci: 22]:
       ├──<GraphBuilderPhase> java.lang.CharacterData.of(int): no, bytecode parser did not replace invoke
       └──<PriorityInliningPhase> java.lang.CharacterData.of(int): yes, worth inlining according to the cost-benefit analysis.
    at java.lang.Character.toUpperCaseEx(Character.java:7138) [bci: 26]:
       ├──<GraphBuilderPhase> java.lang.CharacterDataLatin1.toUpperCaseEx(int): no, bytecode parser did not replace invoke
       └──<PriorityInliningPhase> java.lang.CharacterDataLatin1.toUpperCaseEx(int): yes, worth inlining according to the cost-benefit analysis.
      at java.lang.CharacterDataLatin1.toUpperCaseEx(CharacterDataLatin1.java:223) [bci: 4]:
         ├──<GraphBuilderPhase> java.lang.CharacterDataLatin1.getProperties(int): no, bytecode parser did not replace invoke
         └──<PriorityInliningPhase> java.lang.CharacterDataLatin1.getProperties(int): yes, worth inlining according to the cost-benefit analysis.
    

Diagnostic options

  • -Dgraal.CompilationFailureAction=Silent: Specifies the action to take when compilation fails by throwing an exception.

    The accepted values are:

    • Silent: Print nothing to the console.
    • Print: Print a stack trace to the console.
    • Diagnose: Retry the compilation with extra diagnostics enabled. On VM exit, the collected diagnostics are saved to a zip file that can be submitted along with a bug report. A message is printed to the console describing where the diagnostics file is saved:
      Graal diagnostic output saved in /Users/graal/graal_dumps/1549459528316/graal_diagnostics_22774.zip
      
    • ExitVM: Same as Diagnose except that the VM process exits after retrying.

    For all values except for ExitVM, the VM continues executing.

  • -Dgraal.CompilationBailoutAsFailure=false: The compiler may not complete compilation of a method due to some property or code shape in the method (e.g. exotic uses of the jsr and ret bytecodes). In this case the compilation bails out. If you want to be informed of such bailouts, this option makes GraalVM treat bailouts as failures and thus be subject to the action specified by the -Dgraal.CompilationFailureAction option.
  • -Dgraal.PrintCompilation=false: Prints an informational line to the console for each completed compilation. For example:
    HotSpotCompilation-11  Ljava/lang/Object;                            wait          ()V       |  591ms    12B    92B  4371kB
    HotSpotCompilation-175 Ljava/lang/String;                            lastIndexOf   (II)I     |  590ms   126B   309B  4076kB
    HotSpotCompilation-184 Ljava/util/concurrent/ConcurrentHashMap;      setTabAt      ([Ljava/util/concurrent/ConcurrentHashMap$Node;ILjava/util/concurrent/ConcurrentHashMap$Node;)V  |  591ms    38B    67B  3411kB
    HotSpotCompilation-136 Lsun/nio/cs/UTF_8$Encoder;                    encode        ([CII[B)I |  591ms   740B   418B  4921
    

Setting Compiler Options with Language Launchers

The GraalVM compiler properties above are usable with some other GraalVM launchers such as nodejs and lli. The prefix for specifying the properties is slightly different. For example:

$ java -XX:+EagerJVMCI -Dgraal.ShowConfiguration=info -version

Becomes:

$ js --jvm --vm.Dgraal.ShowConfiguration=info -version

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

JVM Operations Manual

Difference between running the GraalVM compiler in a Native Image vs on the JVM

When running the GraalVM compiler on the JVM, it goes through the same warmup phase that the rest of 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.

To address the issue of taking longer to reach to peak performance, libgraal was introduced – a shared library, produced using Native Image framework to ahead-of-time compile the compiler itself. That means the GraalVM compiler is deployed as a native shared library. In this mode, the compiler uses memory separate from the HotSpot heap and it runs compiled from the start. That is, it has execution properties similar to other native HotSpot compilers such as C1 and C2. Currently, this is the default mode of operation in both GraalVM Community and Enterprise images. It can be disabled with -XX:-UseJVMCINativeLibrary.

Measuring Performance

The first thing to be sure of when measuring the performance is to ensure the JVM is using the GraalVM compiler. In the GraalVM binary, the JVM is configured to use the GraalVM compiler 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 the compiler 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 the GraalVM compiler 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. The 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 the GraalVM compiler, 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 the GraalVM Compiler

Like all software, the GraalVM compiler 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.

If you spot a security vulnerability, please do not report it via GitHub Issues or the public mailing lists, but via the process outlined at Reporting Vulnerabilities guide.

Compilation Exceptions

One advantage of the compiler 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 Silent. Specifying Diagnose 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 its location is printed on the console:

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

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

Apart from Silent and Diagnose the following values for graal.CompilationFailureAction are supported:

  • Print: Prints a message and stack trace to the console but does not do the re-compilation.
  • ExitVM: Same as Diagnose but the VM process exits after the 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 GraalVM 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 the GraalVM compiler 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 the compiler 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 GraalVM compiler diagnostic output or delete the directory it was written to. This must be done manually after the crash.

By default, the directory is $PWD/graal-dumps/<timestamp>; for example, ./graal-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 compiler:

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

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

$ ls -l /Users/demo/graal-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

https://www.graalvm.org/

å¨è¿éæå¥å¾çæè¿°

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/101398957