內容綱要

隨着Red Hat宣佈Quarkus作爲…

爲GraalVM和HotSpot量身定製的下一代Kubernetes原生Java框架,使用一流的Java庫和標準構建

https://quarkus.io

Red Hat展示的Quarkus 示例項目 的啓動速度和內存消耗給我留下了深刻的印象。令人印象深刻的主要原因之一是,代碼是用GraalVM 提前(ahead-of-time,AOT) 編譯成本機映像( native image )的。爲了幫助您更好地瞭解傳統的HotSpot JVM和GraalVM之間的區別,我將在此博客文章中向您介紹GraalVM及其功能和歷史。

TL; DR:GraalVM是Oracle開發的用純Java編寫的JVM擴展,支持多語言編程和提前編譯。

HotSpot Java虛擬機的歷史

多年來,HotSpot是Oracle維護和分發的主要Java虛擬機,用於運行Java程序。 Java HotSpot Performance Engine於1999年發佈,最初由Animorphic開發,該公司被Sun Microsystems收購,現在由Oracle擁有。該虛擬機主要用C / C ++編寫,並且變得越來越複雜(2007年估計有250.000行代碼)。

HotSpot JVM的主要目的是運行Java字節碼(.class文件)並持續分析程序的性能,以查找程序中經常執行的所謂熱點,並即時(JIT,全稱 just-in-time )將其編譯爲本機代碼(機器代碼)以提高性能。這是在運行時完成的,而不是在Java程序執行之前執行的,因此是即時( just-in-time )的。

在HotSpot JVM中運行Java代碼的工作流程如下所示(簡化):

HotSpot虛擬機主要解釋程序提供的Java字節碼,但在分析程序運行時(runtime)發現應用程序中的適當部分時,也會及時將字節碼編譯爲本機代碼。

當使用JIT編譯器編譯一個方法時,當該方法被調用時,jvm將直接執行編譯出來的機器碼,而不是解釋它以此來提高性能。由於編譯本機代碼需要CPU時間和內存,JVM必須決定在運行時編譯哪些方法,因爲將所有方法直接編譯爲本機代碼會影響性能。

Java 9發行版中的更改

藉助Java 9,特別是 JEP 295 ,JDK獲得了 提前(ahead-of-time,AOT) 編譯器jaotc。該編譯器使用OpenJDK項目 Graal 進行後端代碼生成,這樣做的原因如下:

JIT編譯器速度很快,但是Java程序可能非常龐大,以至於JIT完全預熱需要很長時間。很少使用的Java方法可能根本不會被編譯,由於重複的解釋調用可能會導致性能下降

https://openjdk.java.net/jeps/295

Graal OpenJDK項目演示了用純Java編寫的編譯器可以生成高度優化的代碼。使用此AOT編譯器和Java 9,您可以提前手動編譯Java代碼。這意味着在執行之前生成機器代碼,而不是像JIT編譯器那樣在運行時生成代碼,這是第一種實驗性的方法。

# using the new AOT compiler (jaotc is bundeled within JDK 9 and above)
jaotc --output libHelloWorld.so HelloWorld.class
jaotc --output libjava.base.so --module java.base
 
# with Java 9 you have to manually specify the location of the native code
java -XX:AOTLibrary=./libHelloWorld.so,./libjava.base.so HelloWorld

這將改善啓動時間,因爲JIT編譯器不必攔截程序的執行。這種方法的主要缺點是生成的機器代碼依賴於程序所在的平臺(Linux,MacOS,windows...)。這可能導致AOT編譯代碼與特定平臺綁定。

GraalVM的架構

基於Graal編譯器,Oracle開始開發GraalVM,不僅與HotSpots JVM的複雜C/C++代碼庫一起工作,而且還可以通過用Java編寫的虛擬機解決當前的多語言遷移問題。

GraalVM的架構如下所示:

首先,您可能會注意到一些非JVM語言的存在。現在可以在這個通用虛擬機中運行Ruby、R或JavaScript代碼。只是因爲GraalVM採用了Truffle框架。Truffle是一個開源庫,用於構建編程語言實現,作爲自修改(self-modifying)抽象語法樹的解釋器。有了這個特性,您現在可以在Java代碼庫中編寫和執行例如JavaScript代碼。

此外,GraalVM提供了以下功能,可以提前將程序編譯成本機可執行文件:

GraalVM允許您提前將程序編譯爲本地可執行文件。生成的程序不能在Java HotSpot VM上運行,而是使用必要的組件,例如內存管理,來自另一種虛擬機實現的線程調度(稱爲Substrate VM)。SubstrateVM用Java編寫,然後編譯進本地可執行文件。與Java VM相比,生成的程序具有更快的啓動時間和更低的運行時內存開銷。

https://www.graalvm.org/docs/reference-manual/aot-compilation/

使用GraalVM編譯並運行第一個Java程序

撰寫本文時,GraalVM有兩個版本:社區版(CE)和企業版(EE),僅適用於Mac OS X和Linux。要在開發過程中在Windows上使用GraalVM,您可以使用Oracle的官方Docker映像,以下示例中使用了該映像。

想象下面的簡單HelloWorld類:

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

在GraalVM和Java 9的AOT編譯器之前,您執行了如下代碼:

$ javac HelloWorld.java
$ java HelloWorld
Hello World!

藉助GraalVM,您現在可以選擇使用現有方式(HotSpot JVM)運行應用程序,或者使用GraalVM AOT編譯器創建本機映像並運行可執行文件:

$ javac HelloWorld
$ native-image HelloWorld
$ ./helloworld
HelloWorld!

在這個HelloWorld示例中,改進的性能是微不足道的,但是在更大和更現實的應用程序中,性能的改進是顯著的。

在官方的GraalVM入門指南中可以找到一個簡單的多語言應用程序示例:

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));
        System.out.println(result.asString());
      }
    }
}

這個Java類負責漂亮地打印JSON,使用JavaScript方法JSON.parse() 和JSON.stringify() 。此類的本機鏡像可通過如下方式構建:

$ javac PrettyPrintJSON.java
$ native-image --language:js PrettyPrintJSON
$ ./prettyprintjson < prettyMe.json
{
  "GraalVM": {
    "description": "Language Abstraction Platform",
    "supports": [
      "combining languages",
      "embedding languages",
      "creating native images"
    ],
    "languages": [
      "Java",
      "JavaScript",
      "Node.js",
      "Python",
      "Ruby",
      "R",
      "LLVM"
    ]
  }
}

現在可以測量運行本機映像和在HotSpot中運行應用程序之間的性能差異:

$ time bin/java PrettyPrintJSON < prettyMe.json > /dev/null
real    0m1.101s
user    0m2.471s
sys 0m0.237s
 
$ time ./prettyprintjson < prettyMe.json > /dev/null
real    0m0.037s
user    0m0.015s
sys 0m0.016s

在我看來,Oracle和GraalVM在Java作爲編程語言的主導地位方面做得非常好。此外,這一舉措提高了Java語言本身的可持續性和特性開發。有了多語言體系結構,這也增加了其他編程語言的採用。

您可以在我的 GitHub存儲庫 中找到示例,然後直接在GraalVM上(Mac和Linux)或在Windows上的Docker上(確保爲Docker提供至少6 GB的RAM和2-4個內核)進行嘗試。

原文地址:https://rieckpil.de/whatis-graalvm/

:star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2::star2:

歡迎訪問筆者博客: blog.dongxishaonian.tech

關注筆者公衆號,推送各類原創/優質技術文章 :arrow_down:

相關文章