執行示範應用程式

Espresso 是 Java 虛擬機器規格的實作,除了能夠在 Java 或其他 JVM 語言中執行應用程式之外,還提供一些有趣的功能。例如,增強的 HotSwap 功能 可透過啟用無限的熱碼重新載入來提高開發人員的生產力。此外,為了說明 Espresso 可以做什麼,請考慮以下簡短的範例。

混合 Java 的 AOT 和 JIT #

GraalVM Native Image 技術允許將應用程式預先 (AOT) 編譯為可執行原生二進位檔,這些檔案

  • 是獨立的
  • 立即啟動
  • 具有較低的記憶體使用量

使用 Native Image 的主要缺點是,程式的分析和編譯是在封閉世界假設下進行的,這表示靜態分析需要處理應用程式中將執行的所有位元組碼。這使得使用某些語言功能(例如動態類別載入或反射)變得棘手。

Espresso 是基於 Truffle 框架建構的 JVM 位元組碼直譯器 JVM 實作。它本質上是一個 Java 應用程式,Truffle 框架本身和 GraalVM JIT 編譯器也是如此。這三者都可以使用 native-image 預先編譯。在應用程式的某些部分使用 Espresso 可以隔離所需的動態行為,並且仍然可以在其餘程式碼上使用原生可執行檔。

以典型的 Java Shell 工具 (JShell) 為例,它是一個能夠評估 Java 程式碼的 REPL,由兩個部分組成

  • UI - 處理輸入輸出的 CLI 應用程式
  • 用於執行您輸入 Shell 的程式碼的後端處理器。

此設計自然適合我們想要說明的重點。我們可以建構 JShell UI 部分的原生可執行檔,並使其包含 Espresso 以動態執行在執行階段指定的程式碼。

先決條件

使用示範應用程式複製 專案,並導覽至 espresso-jshell 目錄

git clone https://github.com/graalvm/graalvm-demos.git
cd graalvm-demos/espresso-jshell

JShell 實作實際上是正常的 JShell 啟動器程式碼,它只接受執行引擎的 Espresso 實作。

將 AOT 編譯部分與動態評估程式碼的元件連結在一起的「膠水」程式碼位於 EspressoExecutionControl 類別中。它會在 Espresso 環境中載入 JShell 類別,並將輸入委派給它們

protected final Lazy<Value> ClassBytecodes = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ClassBytecodes"));
protected final Lazy<Value> byte_array = Lazy.of(() -> loadClass("[B"));
protected final Lazy<Value> ExecutionControlException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ExecutionControlException"));
protected final Lazy<Value> RunException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$RunException"));
protected final Lazy<Value> ClassInstallException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ClassInstallException"));
protected final Lazy<Value> NotImplementedException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$NotImplementedException"));
protected final Lazy<Value> EngineTerminationException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$EngineTerminationException"));
protected final Lazy<Value> InternalException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$InternalException"));
protected final Lazy<Value> ResolutionException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$ResolutionException"));
protected final Lazy<Value> StoppedException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$StoppedException"));
protected final Lazy<Value> UserException = Lazy.of(() -> loadClass("jdk.jshell.spi.ExecutionControl$UserException"));

還有更多程式碼可以正確傳遞值並轉換例外狀況。若要試用,請使用提供的腳本建構 espresso-jshell 二進位檔,該腳本將

  1. 將 Java 來源建構成位元組碼
  2. 建構 JAR 檔案
  3. 建構原生可執行檔

建構後,您可以觀察產生的二進位檔(fileldd 是 Linux 命令)

file ./espresso-jshell
ldd ./espresso-jshell

它確實是一個不依賴 JVM 的二進位檔,您可以執行它,並注意到它啟動的速度有多快

./espresso-jshell
|  Welcome to JShell -- Version 11.0.10
|  For an introduction type: /help intro

jshell> 1 + 1
1 ==> 2

實驗將新程式碼載入 JShell,並查看 Espresso 如何執行它。

觀看混合 AOT 和 JIT 編譯程式碼與 Espresso 示範的影片版本。


搭配 Espresso 的 GraalVM 工具 #

Espresso 是 GraalVM 生態系統的適當部分,並且與其他 GraalVM 支援的語言一樣,預設會取得開發人員工具的支援。Truffle 框架與除錯器、分析器、記憶體分析器、Instrumentation API 等工具整合。語言的直譯器需要用一些註釋標記 AST 節點,以支援這些工具。

例如,若要能夠使用分析器,語言直譯器需要標記根節點。為了除錯目的,應該將語言表達式標記為可檢測的,並指定變數的範圍等等。語言直譯器不需要與工具本身整合。因此,您可以開箱即用地使用 CPU 取樣器或記憶體追蹤器工具,在 Espresso 上分析 Java 應用程式。

例如,如果我們有一個類似以下計算質數的類別

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.LongStream;

public class Main {

    public static void main(String[] args) {
        Main m = new Main();

        for (int i = 0; i < 100_000; i++) {
            System.out.println(m.random(100));
        }
    }

    private Random r = new Random(41);
    public List<Long> random(int upperbound) {
        int to = 2 + r.nextInt(upperbound - 2);
        int from = 1 + r.nextInt(to - 1);
        return primeSequence(from, to);
    }
    public static List<Long> primeSequence(long min, long max) {
        return LongStream.range(min, max)
                .filter(Main::isPrime)
                .boxed()
                .collect(Collectors.toList());
    }
    public static boolean isPrime(long n) {
        return LongStream.rangeClosed(2, (long) Math.sqrt(n))
                .allMatch(i -> n % i != 0);
    }
}

建構此程式,並使用 --cpusampler 選項執行它。

javac Main.java
java -truffle --cpusampler Main > output.txt

output.txt 檔案的結尾,您會找到分析器的輸出、方法的直方圖以及執行所花費的時間。您也可以嘗試使用 --memtracer 選項進行實驗,以查看此程式中配置發生在何處。

java -truffle --experimental-options --memtracer Main > output.txt

GraalVM 提供的其他工具包括 Chrome 除錯器程式碼涵蓋率GraalVM Insight

由於對開發人員工具的「開箱即用」支援,Espresso 成為 JVM 的有趣選擇。

觀看 Espresso 的 GraalVM 內建工具的簡短示範。


與我們聯繫