GraalVM で Java ネイティブイメージビルドで Hello World する

# GraalVM で Java ネイティブイメージビルドで Hello World する

こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 24.04 に GraalVM をインストールする手順と、Java ネイティブイメージビルドで Hello World を出力する方法を紹介します。
![graalvm_on_ubuntu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/d8e040ee-93fe-94cb-2552-04f5cd292b3c.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8

## 実現すること

ローカル環境 (Ubuntu) で Java アプリを GraalVM を使用してネイティブイメージビルドし、作成したアプリケーションを実行してみます。

## 技術トピック

### Java におけるネイティブイメージビルドとは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

:::note info
|キーワード|内容|
|:--|:--|
|ネイティブイメージビルド|Java コードをネイティブマシンコードにコンパイルすることです。通常、Java コードは Java バイトコードと呼ばれる中間言語にコンパイルされ、Java 仮想マシン (JVM) で実行されます。しかし、**ネイティブイメージビルド**は、Java コードを **JVM を介さず**に直接実行可能な**ネイティブマシンコードに変換**することで、**より高速な実行速度**と**より低いメモリ使用量**を実現することができます。|

ネイティブイメージビルドは、以下のようなニーズから求められています。  

|キーワード|内容|
|:--|:--|
|パフォーマンスの向上|Java は一般的に高水準のプログラミング言語であり、JVM によって実行されるため、実行速度が遅いとされることがあります。ネイティブイメージビルドにより、高速な実行速度を実現することができます。|
|メモリの最適化|JVM による Java コードの実行には、多くのメモリが必要となることがあります。ネイティブイメージビルドにより、より少ないメモリ使用量でプログラムを実行できるようになります。|
|ネイティブプログラムの統合|Java は、C言語や C++ などの他のプログラミング言語で書かれたネイティブプログラムと統合することができます。しかし、統合するためには、ネイティブコードが必要になります。ネイティブイメージビルドにより、これらのネイティブプログラムと Java コードをシームレスに統合することができます。|
:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 2.2.4.0
カーネル バージョン: 5.15.153.1-2
WSLg バージョン: 1.0.61
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04 LTS
Release:        24.04
Codename:       noble
```

Java JDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/4d11e94a2389758759cd)

```sh
$ java -version
openjdk version "11.0.23" 2024-04-16
OpenJDK Runtime Environment (build 11.0.23+9-post-Ubuntu-1ubuntu1)
OpenJDK 64-Bit Server VM (build 11.0.23+9-post-Ubuntu-1ubuntu1, mixed mode, sharing)
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463


## GraalVM ビルド環境の構築

### GraalVM とは？

:::note info
**GraalVM** は、Oracle によって開発された Java 仮想マシンの1つで、Java や Kotlin、Scala などの様々な言語の実行や、ネイティブバイナリへの変換が可能なマルチ言語ランタイム環境です。**GraalVM** は、通常の Java 仮想マシンよりも高速に実行することができ、また、Java コードをネイティブバイナリに変換することによって、より高速で軽量なアプリケーションを実行できるようになります。また、JavaScript エンジンである Node.js をホストすることもでき、複数の言語の統合において優れた柔軟性を持っています。
:::

### GraalVM インストール

Ubuntu で Native Image ビルドを行うためには以下の手順が必要です。  

:::note warn
この記事では手動でインストールする方法を紹介しています。  
:::

GraalVM を手動で **/usr/lib/jvm/** にインストールします。

```sh
$ cd ~
$ wget https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-17.0.9/graalvm-community-jdk-17.0.9_linux-x64_bin.tar.gz
$ sudo mkdir -p /usr/lib/jvm/graalvm-ce-java17-linux-amd64
$ sudo tar zxvf graalvm-community-jdk-17.0.9_linux-x64_bin.tar.gz -C /usr/lib/jvm/graalvm-ce-java17-linux-amd64 --strip-components=1
```

インストールされた GraalVM のパスを確認します。

```sh
$ ls -la  /usr/lib/jvm/
graalvm-ce-java17-linux-amd64
```

現在インストールされた JVM を確認します。  
※ そのまま Enter します。

```sh
$ sudo update-alternatives --config java
  選択肢    パス                                          優先度  状態
------------------------------------------------------------
  0            /usr/lib/jvm/java-17-openjdk-amd64/bin/java      1711      自動モード
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java      1111      手動モード
* 2            /usr/lib/jvm/java-17-openjdk-amd64/bin/java      1711      手動モード
  3            /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java   1081      手動モード
```

GraalVM を update-alternatives に設定します。  
※ 優先順位の数値を 1017 (※任意) に設定しています。  
※ GraalVM の優先順位を下げる意図があります。  

```sh
$ sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/graalvm-ce-java17-linux-amd64/bin/java 1017
$ sudo update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/graalvm-ce-java17-linux-amd64/bin/javac 1017
```

GraalVM に切り替えてみます。  
※ GraalVM の番号を選択します。

```sh
$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac
```

<details>
<summary>削除する場合と自動モードに戻す場合</summary>

※ 削除する場合

```sh
$ sudo update-alternatives --remove java /usr/lib/jvm/graalvm-ce-java17-linux-amd64/bin/java
$ sudo update-alternatives --remove javac /usr/lib/jvm/graalvm-ce-java17-linux-amd64/bin/javac
```

※ 自動モードに戻す場合

```sh
$ sudo update-alternatives --auto java
$ sudo update-alternatives --auto javac
```

</details>

JAVA_HOME を動的に設定しているので Ubuntu に再ログインします。  
※ WSL の場合ターミナルを閉じて再度開きます。    

バージョンを確認します。  

```sh
$ java -version
openjdk version "17.0.9" 2023-10-17
OpenJDK Runtime Environment GraalVM CE 17.0.9+9.1 (build 17.0.9+9-jvmci-23.0-b22)
OpenJDK 64-Bit Server VM GraalVM CE 17.0.9+9.1 (build 17.0.9+9-jvmci-23.0-b22, mixed mode, sharing)
```

JVM に GraalVM を設定することが出来ています。  

GraalVM Updater のバージョンを確認します。  

```sh
$ gu --version
GraalVM Updater 23.0.2
```

:::note info
GraalVM Updater は、GraalVM のインストール、アップデート、アンインストールを行うためのツールです。 **gu** は、GraalVM Updater のコマンドです。
:::

Native Image コマンドをインストール(確認)します。

```sh
$ sudo $(which gu) install native-image
Downloading: Component catalog from www.graalvm.org
Processing Component: Native Image
Component Native Image (org.graalvm.native-image) is already installed.
```

GraalVM Native Image のバージョンを確認します。

```sh
$ native-image --version
native-image 17.0.9 2023-10-17
GraalVM Runtime Environment GraalVM CE 17.0.9+9.1 (build 17.0.9+9-jvmci-23.0-b22)
Substrate VM GraalVM CE 17.0.9+9.1 (build 17.0.9+9, serial gc)
```

:::note info
GraalVM Native Image は、Java アプリケーションをネイティブコードにコンパイルし、高速かつ軽量なネイティブアプリケーションを生成するためのツールです。
:::

ネイティブバイナリへのビルドに必要なライブラリをインストールします。

```sh
$ sudo apt update
$ sudo apt install build-essential libz-dev zlib1g-dev
```

これで GraalVM ビルド環境がインストールされました。

## Hello World を表示する手順

### プロジェクトの作成

プロジェクトフォルダを作成します。  
※ ~/tmp/hello-graalvm をプロジェクトフォルダとします。

```sh
$ mkdir -p ~/tmp/hello-graalvm
$ cd ~/tmp/hello-graalvm
```

### Java クラスの作成

Java ソースファイルを作成します。

```sh
$ vim HelloWorld.java
```

ファイルの内容

```java:HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}
```

### JDK のバージョン確認

:::note warn
**GraalVM** が選択されているか確認してください。
:::

```sh
$ java -version
openjdk version "17.0.9" 2023-10-17
OpenJDK Runtime Environment GraalVM CE 17.0.9+9.1 (build 17.0.9+9-jvmci-23.0-b22)
OpenJDK 64-Bit Server VM GraalVM CE 17.0.9+9.1 (build 17.0.9+9-jvmci-23.0-b22, mixed mode, sharing)
```

### コンパイル

Java ソースファイルをネイティブマシンコードにコンパイルします。

```sh
$ javac HelloWorld.java
$ native-image HelloWorld
```

<details>
<summary>詳細な出力を表示します。</summary>

```sh
$ native-image HelloWorld
========================================================================================================================GraalVM Native Image: Generating 'helloworld' (executable)...
========================================================================================================================
[1/8] Initializing...                                                                                    (3.4s @ 0.13GB)
 Java version: 17.0.9+9, vendor version: GraalVM CE 17.0.9+9.1
 Graal compiler: optimization level: 2, target machine: x86-64-v3
 C compiler: gcc (linux, x86_64, 13.2.0)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
[2/8] Performing analysis...  [****]                                                                     (6.8s @ 0.36GB)
   2,912 (71.67%) of  4,063 types reachable
   3,529 (50.93%) of  6,929 fields reachable
  13,204 (43.87%) of 30,098 methods reachable
     910 types,     0 fields, and   351 methods registered for reflection
      58 types,    58 fields, and    52 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/8] Building universe...                                                                               (1.4s @ 0.32GB)
[4/8] Parsing methods...      [*]                                                                        (0.8s @ 0.52GB)
[5/8] Inlining methods...     [***]                                                                      (0.5s @ 0.59GB)
[6/8] Compiling methods...    [***]                                                                      (6.7s @ 0.82GB)
[7/8] Layouting methods...    [*]                                                                        (0.8s @ 0.45GB)
[8/8] Creating image...       [*]                                                                        (1.8s @ 0.59GB)
   4.41MB (36.70%) for code area:     7,500 compilation units
   7.03MB (58.48%) for image heap:   89,263 objects and 5 resources
 593.34kB ( 4.82%) for other data
  12.02MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 origins of code area:                                Top 10 object types in image heap:
   3.36MB java.base                                         1007.54kB byte[] for code metadata
 795.67kB svm.jar (Native Image)                             892.40kB byte[] for general heap data
 112.32kB java.logging                                       889.03kB java.lang.String
  62.07kB org.graalvm.nativeimage.base                       672.10kB java.lang.Class
  24.15kB jdk.internal.vm.ci                                 665.55kB byte[] for java.lang.String
  23.14kB org.graalvm.sdk                                    347.48kB java.util.HashMap$Node
   6.11kB jdk.internal.vm.compiler                           250.25kB com.oracle.svm.core.hub.DynamicHubCompanion
   1.35kB jdk.proxy1                                         168.86kB java.lang.String[]
   1.27kB jdk.proxy3                                         165.67kB java.lang.Object[]
   1.18kB jdk.localedata                                     148.84kB byte[] for embedded resources
  468.00B for 2 more packages                                  1.20MB for 827 more object types
------------------------------------------------------------------------------------------------------------------------
Recommendations:
 HEAP: Set max heap for improved and more predictable memory usage.
 CPU:  Enable more CPU features with '-march=native' for improved performance.
------------------------------------------------------------------------------------------------------------------------
                        1.2s (4.9% of total time) in 54 GCs | Peak RSS: 1.37GB | CPU load: 8.58
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /home/hiroxpepe/tmp/hello-graalvm/helloworld (executable)
========================================================================================================================
Finished generating 'helloworld' in 22.6s.
```

</details>


### ディレクトリ構成

プロジェクトのファイル構成を表示してみます。

```sh
$ tree
.
├── HelloWorld.class
├── HelloWorld.java
└── helloworld
```

```sh
$ ls -lah
合計 13M
drwxr-xr-x 2 $USER $USER 4.0K  7月 14 19:15 .
drwxr-xr-x 7 $USER $USER 4.0K  7月 14 19:13 ..
-rw-r--r-- 1 $USER $USER  426  7月 14 19:14 HelloWorld.class
-rw-r--r-- 1 $USER $USER  123  7月 14 19:14 HelloWorld.java
-rwxr-xr-x 1 $USER $USER  13M  7月 14 19:15 helloworld
```

**helloworld** というネイティブイメージビルドのアプリケーションが作成されています。

### アプリ実行

ネイティブイメージを実行します。

```sh
$ ./helloworld
Hello World!
```

ターミナルに "Hello World!" と表示することが出来ました。

## まとめ

+ Ubuntu に GraalVM をインストールして、Java のネイティブイメージビルドした実行可能ファイルを作成、実行することが出来ました。

:::note warn
この記事は GraalVM をインストールして、Java のネイティブイメージをビルドし、アプリを作成、実行することを目的としています。**現時点では**通常のビルド方法と比べて、**明確なメリットがあるわけではありません**。しかしながら、今後の記事では GraalVM の利点や、ネイティブイメージビルドによるアプリの実行速度の向上などを紹介していく予定です。
:::

どうでしたか？ Window 11 の WSL Ubuntu に、GraalVM を使用した Java のネイティブイメージビルド環境を手軽に構築することができます。ぜひお試しください。今後も Java の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 参考資料

#### GraalVM

https://www.graalvm.org/latest/reference-manual/java/