- GraalVM for JDK 23 (最新)
- GraalVM for JDK 24 (アーリーアクセス)
- GraalVM for JDK 21
- GraalVM for JDK 17
- アーカイブ
- 開発ビルド
デモアプリケーションの実行
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
バイナリをビルドします。これにより、次の処理が行われます。
- Javaソースをバイトコードにビルドする
- JARファイルをビルドする
- ネイティブ実行可能ファイルをビルドする
ビルド後、結果のバイナリファイルを確認できます(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に新しいコードをロードして実験し、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組み込みツールの簡単なデモをご覧ください。