GraalVMを学びながら動した (Windows)

# 初めに
- 最近「GraalVM」について調べたのでまとめます
- 実行環境は以下です
    - Windows 10
    - GraalVM Community Edition 22.3.1
    - Visual Studio 2022  (Native イメージのコンパイルで必要)
- 誤り等あれば、ご指摘いただけますと幸いです

# 本書の対象
- JVMやJITコンパイラなど、基本的なJavaの用語の理解が必要です
- 不安な方は、本記事の[【復習】Javaはどのように動くのか](#【復習】Javaはどのように動くのか) をご確認ください
    - 筆者も最初はかなり忘れていました
 
# GraalVMとは

- 複数のプログラム言語を高パフォーマンスで実行できるランタイムプラットフォーム。特徴は以下。
    - Graal
        - GraalVM上で動作する新しいJITコンパイラ(C2)
    - Truffle (多言語プラットフォーム)
        - Java、JavaScript、Ruby、Python 等の様々な言語を実行可能
        - ある言語から別の言語のAPIを呼び出すことも可能 (JavaからJavaScriptのAPIを呼び出す)
        - 独自の言語実装も追加できる
    - Native Image
        - AOT(Ahead Of Tim)コンパイルにより、Nativeイメージを作成できる
        - メモリ使用量が低く、起動時間も短いことから、コンテナアプリケーションとの相性が良い

# なぜ、GraalVMが注目されているの？
- CloudNativeが主流になる中、Javaの重たさや起動処理の遅さが問題視されるようになった
- GraalVMの出現により、Javaのデメリットを解消できるのではと期待されている

## 環境構築
### 1. GraalVMのインストール
- [GraalVMインストール](https://github.com/graalvm/graalvm-ce-builds/releases)からGraalVMをインストールする。
    - 筆者の場合は、Windows (amd64) × Java 17 を選択
    - ダウンロードされるZIPファイルを`C:\Program Files\graalvm`に展開
    - 環境変数PATH/JAVAHOMEを以下のように設定
        - PATH に `C:\Program Files\graalvm\bin` を追加
        - JAVAHOMEに`C:\Program Files\graalvm`を設定
    - コマンドプロンプトに`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](https://visualstudio.microsoft.com/ja/vs/whatsnew/)から「Community Edition」を選択してインストーラをDLする
   - インストーラの[デスクトップとモバイル ] -> [C++ によるデスクトップ開発] を選択してインストール
   - インストール完了後、OSを再起動

# 動作確認
## 動作確認用のプログラムを作成する
- 素数を計算するプログラム。処理量を多くしたいので、最適化はしてません
```java
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に変化している
  ```
  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, ・・・略 ★
  ・・略・・
  ```
  OpenJDK17の場合
  ```
  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を動かす](https://qiita.com/yitakura731/items/9d96b87c5e133c0ba0d8)  にまとめているのでご確認ください。

## Native Image を作って実行する

- AOTコンパイラによりJavaをNativeイメージにすることで、VMと比較して高速起動できる
    - AOTコンパイラ：Ahead-Of-Timeコンパイラの略。アプリ実行前に事前にプログラムを機械語に変換する

1. 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
    ```

1. Visual Studio 2022 Developerコンソールを開く
    - このコンソールで実行しないと、Native イメージの作成時に必要なモジュールが参照できず、エラーになるので注意

1. 以下のコマンドを入力
    ```
    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.
    ```

1. 以下を入力し、Native イメージ化されたexeで同様の処理が実行できる事を確認する
    ```
    app-1.0.0.exe
    ```

# 【復習】Javaはどのように動くのか

あらためて、Javaはどのように動作するのか復習します。
<img width="600px" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361228/204c1655-94b1-f10a-c1e4-78b2e8890459.png">
1. 人間がプログラムを書く。
1. コンパイラがプログラムをバイトコード(中間ファイル)に変更する
    - javacコマンドにより生成される.classファイル
1. 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