デモアプリケーションの実行

EspressoはJava Virtual Machine仕様の実装であり、Javaやその他のJVM言語でアプリケーションを実行できることに加えて、いくつかの興味深い機能を提供します。たとえば、拡張されたホットスワップ機能は、無制限のホットコードリローディングを可能にすることで、開発者の生産性を向上させます。さらに、Espressoで何ができるかを説明するために、以下の簡単な例を考えてみましょう。

JavaのAOTとJITの混合 #

GraalVM Native Imageテクノロジーを使用すると、アプリケーションを事前にコンパイル(AOT)して、実行可能なネイティブバイナリにすることができます。

  • スタンドアロンである
  • すぐに起動する
  • メモリ使用量が少ない

Native Imageを使用する主なトレードオフは、プログラムの分析とコンパイルがクローズドワールドの前提の下で行われることです。つまり、静的分析では、アプリケーションで実行されるすべてのバイトコードを処理する必要があります。これにより、動的なクラスローディングやリフレクションなどの一部の言語機能の使用が困難になります。

Espressoは、Truffleフレームワーク上に構築されたJVMバイトコードインタプリタのJVM実装です。本質的には、Truffleフレームワーク自体やGraalVM JITコンパイラと同様にJavaアプリケーションです。これら3つすべては、native-imageを使用して事前にコンパイルできます。アプリケーションの一部にEspressoを使用することで、必要な動的動作を分離し、残りのコードでネイティブ実行可能ファイルを引き続き使用できます。

コマンドラインアプリケーションの例として、正規のJava Shellツール(JShell)を考えてみましょう。これはJavaコードを評価できるREPLであり、次の2つの部分で構成されています。

  • UI - 入出力を処理するCLIアプリ
  • シェルに入力したコードを実行するためのバックエンドプロセッサ。

この設計は、私たちが説明しようとしているポイントに自然に適合します。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がどのように実行するかを確認してください。

Espressoデモを使用して、AOTとJITコンパイルされたコードを混合するビデオ版をご覧ください。


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組み込みツールの簡単なデモをご覧ください。


お問い合わせ