初めに
- 最近「GraalVM」について調べたのでまとめます
- 実行環境は以下です
- Windows 10
- GraalVM Community Edition 22.3.1
- Visual Studio 2022 (Native イメージのコンパイルで必要)
- 誤り等あれば、ご指摘いただけますと幸いです
本書の対象
- JVMやJITコンパイラなど、基本的なJavaの用語の理解が必要です
- 不安な方は、本記事の【復習】Javaはどのように動くのか をご確認ください
- 筆者も最初はかなり忘れていました
GraalVMとは
- 複数のプログラム言語を高パフォーマンスで実行できるランタイムプラットフォーム。特徴は以下。
- Graal
- GraalVM上で動作する新しいJITコンパイラ(C2)
- Truffle (多言語プラットフォーム)
- Java、JavaScript、Ruby、Python 等の様々な言語を実行可能
- ある言語から別の言語のAPIを呼び出すことも可能 (JavaからJavaScriptのAPIを呼び出す)
- 独自の言語実装も追加できる
- Native Image
- AOT(Ahead Of Tim)コンパイルにより、Nativeイメージを作成できる
- メモリ使用量が低く、起動時間も短いことから、コンテナアプリケーションとの相性が良い
- Graal
なぜ、GraalVMが注目されているの?
- CloudNativeが主流になる中、Javaの重たさや起動処理の遅さが問題視されるようになった
- GraalVMの出現により、Javaのデメリットを解消できるのではと期待されている
環境構築
1. GraalVMのインストール
-
GraalVMインストールからGraalVMをインストールする。
- 筆者の場合は、Windows (amd64) × Java 17 を選択
- ダウンロードされるZIPファイルを
C:\Program Files\graalvm
に展開 - 環境変数PATH/JAVAHOMEを以下のように設定
- PATH に
C:\Program Files\graalvm\bin
を追加 - JAVAHOMEに
C:\Program Files\graalvm
を設定
- PATH に
- コマンドプロンプトに
java --version
と入力し、GraalVMのJVMのバージョンが表示される事を確認する
openjdk 17.0.6 2023-01-17 OpenJDK Runtime Environment GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13) OpenJDK 64-Bit Server VM GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13, mixed mode, sharing
- コマンドプロンプトに
native-image --version
と入力し、GraalVMのNativeイメージのバージョンが表示されることを確認する
GraalVM 22.3.1 Java 17 CE (Java Version 17.0.6+10-jvmci-22.3-b13)
2. Visual Studio のインストール
- GraaalVMでNativeイメージを作成するために必要
- Visual Studio 2022から「Community Edition」を選択してインストーラをDLする
- インストーラの[デスクトップとモバイル ] -> [C++ によるデスクトップ開発] を選択してインストール
- インストール完了後、OSを再起動
動作確認
動作確認用のプログラムを作成する
- 素数を計算するプログラム。処理量を多くしたいので、最適化はしてません
public class Sample {
private static final long MAX_SIZE = 200000;
public static void main(String[] args) {
int counter = 1;
int number = 1;
int index = 1;
while (counter < MAX_SIZE) {
boolean hit = true;
if (counter > 2) {
for (int index1 = 2; index1 < counter; index1++) {
if (counter % index1 == 0) {
hit = false;
break;
}
}
}
if (hit) {
number = counter;
index++;
System.out.println(String.format("%d番目の素数は%d", index, number));
}
counter++;
}
}
}
GraalVMでJavaを実行する
- 以下のコマンドを実行する
java -XX:+CITime Sample.java
- 結果は以下の通り。C2コンパイラの表記がGraalVMのJVMCIに変化している
OpenJDK17の場合
Individual compiler times (for compiled methods only) ------------------------------------------------ C1 {speed: 164062.898 bytes/s; standard: 0.594 s, 97329 bytes, ・・・略 JVMCI-native {speed: 47031.351 bytes/s; standard: 0.425 s, 21046 bytes, ・・・略 ★ ・・略・・
Individual compiler times (for compiled methods only) ------------------------------------------------ C1 {speed: 209886.496 bytes/s; standard: 0.383 s, 80375 bytes, ・・・略 C2 {speed: 23532.082 bytes/s; standard: 0.753 s, 18235 bytes, ・・・略 ★ ・・略・・
Java以外の言語を実行する
- GraalVMでは、Java以外のコードを実行したり、Javaから別言語のコードを実行できる。
- 詳しくは、VSCodeでGraalVMを動かす にまとめているのでご確認ください。
Native Image を作って実行する
- AOTコンパイラによりJavaをNativeイメージにすることで、VMと比較して高速起動できる
- AOTコンパイラ:Ahead-Of-Timeコンパイラの略。アプリ実行前に事前にプログラムを機械語に変換する
-
Jarを作成する
- サンプルプログラムをJarファイル化する。方法はたくさんあるけど、今回はコマンドで行う
- コンソールに以下を実行
javac -d classes Sample.java
- classesフォルダにsample.classが作成されていることを確認
-
MANIFEST.MF
を作成し、以下を記述する。必ず改行すること
Main-Class: Sample
- 以下を実行し、実行後に
app-1.0.0.jar
が作成されていることを確認
jar cvfm app-1.0.0.jar MANIFEST.MF -C classes .
- 以下を実行し、正常にJarが作成されていることを確認
java -jar app-1.0.0.jar
-
Visual Studio 2022 Developerコンソールを開く
- このコンソールで実行しないと、Native イメージの作成時に必要なモジュールが参照できず、エラーになるので注意
-
以下のコマンドを入力
native-image -jar app-1.0.0.jar
実行後、下記のように表示され、
app-1.0.0.exe
が作成されることを確認する======================================================================================================================== GraalVM Native Image: Generating 'app-1.0.0' (executable)... ======================================================================================================================== [1/7] Initializing... (7.5s @ 0.11GB) Version info: 'GraalVM 22.3.1 Java 17 CE' Java version info: '17.0.6+10-jvmci-22.3-b13' C compiler: cl.exe (microsoft, x64, 19.35.32215) Garbage collector: Serial GC [2/7] Performing analysis... [*****] (13.4s @ 0.67GB) 2,799 (74.20%) of 3,772 classes reachable 3,376 (50.81%) of 6,644 fields reachable 12,424 (43.11%) of 28,818 methods reachable 27 classes, 0 fields, and 313 methods registered for reflection 62 classes, 53 fields, and 52 methods registered for JNI access 1 native library: version [3/7] Building universe... (1.9s @ 1.14GB) [4/7] Parsing methods... [*] (1.5s @ 0.62GB) [5/7] Inlining methods... [***] (0.9s @ 1.00GB) [6/7] Compiling methods... [***] (11.5s @ 0.80GB) [7/7] Creating image... (2.2s @ 1.20GB) 4.14MB (36.82%) for code area: 7,124 compilation units 6.92MB (61.58%) for image heap: 96,905 objects and 5 resources 184.09KB ( 1.60%) for other data 11.23MB in total ------------------------------------------------------------------------------------------------------------------------ Top 10 packages in code area: Top 10 object types in image heap: 665.50KB java.util 908.06KB java.lang.String 332.85KB java.lang 890.88KB byte[] for code metadata 266.91KB java.text 888.33KB byte[] for general heap data 218.85KB java.util.regex 615.44KB java.lang.Class 196.40KB java.util.concurrent 541.42KB byte[] for java.lang.String 149.10KB java.math 439.13KB java.util.HashMap$Node 127.51KB com.oracle.svm.core.code 220.28KB char[] 120.48KB com.oracle.svm.core.genscavenge 218.67KB com.oracle.svm.core.hub.DynamicHubCompanion 117.19KB java.lang.invoke 212.44KB java.util.HashMap$Node[] 104.38KB java.util.logging 160.30KB java.lang.String[] 1.84MB for 111 more packages 1.53MB for 765 more object types ------------------------------------------------------------------------------------------------------------------------ 0.7s (1.8% of total time) in 20 GCs | Peak RSS: 2.64GB | CPU load: 5.34 ------------------------------------------------------------------------------------------------------------------------ Produced artifacts: app-1.0.0.build_artifacts.txt (txt) app-1.0.0.exe (executable) ======================================================================================================================== Finished generating 'app-1.0.0' in 40.7s.
-
以下を入力し、Native イメージ化されたexeで同様の処理が実行できる事を確認する
app-1.0.0.exe
【復習】Javaはどのように動くのか
- 人間がプログラムを書く。
- コンパイラがプログラムをバイトコード(中間ファイル)に変更する
- javacコマンドにより生成される.classファイル
- JVMがバイトコードを実行する
- JVMはOSごとに用意されるがバイトコードはOSに依存しない。このため、JavaはOSに依存しない言語とされている
- JVMがバイトコードを実行する時は、後述のインタプリタとJITコンパイルの両方で実行される
インタプリタ
- バイトコードを逐次実行する方法
- 逐次実行のため遅い
- そのため頻出する処理はJITコンパイルラで処理する
JITコンパイラ
- Just in time コンパイラの略で、頻出するバイトコードをOSの機械語に変換して処理する方法
- インタプリタと比較して高速だが、コンパイル自体の時間がオーバーヘッドになる
- JITコンパイラは以下の2種類
種類 タイミング 最適化度合い コンパイル時間 クライアントコンパイラ(C1) 初期段階でコンパイル 低い 早い サーバーコンパイラ(C2) 必要な最適化情報を集めてからコンパイル 高い 遅い - Java8移行、C1とC2を併用する階層型コンパイラがデフォルトになっている
動作確認
-
GraalVMの動作確認でも利用した素数を計算するコードを以下の条件で実行して、処理時間を比較した。
条件 処理時間 実行コマンド インタプリタのみ 17.87秒 java -Xint Sample.java インタプリタ+階層型コンパイル 4.89秒 java -XX:+CITime Sample.java -
階層型コンパイルが有効の場合、逐次実行するインタプリタのみと比較してかなり処理時間が早い
用語
JVM
- Java Visual Machineの略
- Javaで作成されたプログラムを、Windows / MacOS / LinuxのOS上で実行するもの
JRE:
- Java Runtime Environment の略
- Javaで作られたプログラムを実行するために必要なソフト
- JVMはJREに含まれる
JDK:
- Java Development Kit の略
- Javaで開発・実行するための総合的なツール群。以下を含む
- ソースコードをコンパイルをするツール
- コードをデバッグするツール
- JREもJDKの一部
引用
https://tech.uzabase.com/entry/2020/01/27/090000
https://speakerdeck.com/oracle4engineer/graalvm-whats-new?slide=55