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

Java on TruffleはJava仮想マシン仕様の実装となり、Javaまたは他のJVM言語でアプリケーションを実行できる以外に、いくつかの興味深い機能を提供します。たとえば、拡張されたホットスワップ機能により、無制限のホット・コードのリロードを可能にすることで開発者の生産性が向上します。Java on Truffleの機能を説明するために、次の簡単な例について考えてみます。

JavaでのAOTとJITの混合

GraalVMネイティブ・イメージテクノロジを使用すると、アプリケーションを次のような実行可能なネイティブ・バイナリにAhead-of-Time (AOT)コンパイルできます:

ネイティブ・イメージを使用する場合の主なトレードオフは、プログラムの分析およびコンパイルが閉世界仮説の下で発生すること、つまり、アプリケーションで実行されるすべてのバイトコードを静的分析で処理する必要があることです。これにより、動的なクラス・ロードやリフレクションなどの一部の言語機能の使用が複雑になります。

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

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

この設計は、例で説明しようとしている点にそのまま適合しています。JShellのUI部分のネイティブ実行可能ファイルをビルドし、それにJava on Truffleを含めて、実行時に動的に指定されたコードを実行できます。

前提条件:

  1. デモ・アプリケーションを含むプロジェクトをクローニングし、espresso-jshellディレクトリに移動します:
git clone https://github.com/graalvm/graalvm-demos.git
cd graalvm-demos/espresso-jshell

JShell実装は実際には通常のJShellランチャ・コードであり、これは実行エンジンのJava on Truffle実装(プロジェクト・コード名はEspresso)のみを受け入れます。

AOTコンパイル済の部分とコードを動的に評価するコンポーネントをバインドするグルー・コードは、EspressoExecutionControlクラスにあります。これは、Java on Truffleコンテキスト内に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. ネイティブ実行可能ファイルをビルドします

native-imageコマンドの最も重要な構成行は、Java on Truffle実装をバイナリに含めるように指定する--language:javaです。ビルド後に、結果のバイナリ・ファイルを確認できます(fileおよびlddは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にロードしてみて、Java on Truffleによってどのように実行されるかを確認します。

Java on TruffleでのAOTとJITの混合コンパイル済コードのデモのビデオ・バージョンを視聴してください。


Java on TruffleでのGraalVMツール

Java on TruffleはGraalVMエコシステムに正式に含まれており、GraalVMでサポートされている他の言語と同様に、開発者ツールもデフォルトでサポートされています。Truffleフレームワークは、デバッガ、プロファイラ、メモリー・アナライザ、インストゥルメンテーションAPIなどのツールと統合されています。言語のインタプリタは、これらのツールをサポートするためにASTノードを注釈でマークする必要があります。

たとえば、プロファイラを使用できるように、言語インタプリタはルート・ノードをマークする必要があります。デバッガ用には、言語式をインストゥルメンタル、指定された変数のスコープなどとしてマークすることをお薦めします。言語のインタプリタは、ツール自体と統合する必要はありません。そのため、CPUサンプラまたはメモリー・トレーサ・ツールを使用して、Java on Truffleプログラムをそのままプロファイリングできます。

たとえば、素数を計算する次のようなクラスがあるとします:

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があります。

開発者ツールが初期状態でサポートされるため、Java on TruffleはJVMの有力な選択肢となります。

Java on TruffleのGraalVM組込みツールの短いデモを視聴してください。