デモ・アプリケーションの実行
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つの部分で構成されています:
- UI - 入出力を処理するCLIアプリケーション
- シェルに入力したコードを実行するためのバックエンド・プロセッサ。
この設計は、例で説明しようとしている点にそのまま適合しています。JShellのUI部分のネイティブ実行可能ファイルをビルドし、それにJava on Truffleを含めて、実行時に動的に指定されたコードを実行できます。
前提条件:
- デモ・アプリケーションを含むプロジェクトをクローニングし、
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
バイナリをビルドします:
- Javaソースをバイトコードにビルドします
- JARファイルを構築します
- ネイティブ実行可能ファイルをビルドします
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組込みツールの短いデモを視聴してください。