GraalVM で Java ネイティブイメージビルドで Hello World する
こんにちは、@studio_meowtoon です。今回は、WSL の Ubuntu 24.04 に GraalVM をインストールする手順と、Java ネイティブイメージビルドで Hello World を出力する方法を紹介します。
目的
Windows 11 の Linux でクラウド開発します。
こちらから記事の一覧がご覧いただけます。
実現すること
ローカル環境 (Ubuntu) で Java アプリを GraalVM を使用してネイティブイメージビルドし、作成したアプリケーションを実行してみます。
技術トピック
Java におけるネイティブイメージビルドとは?
こちらを展開してご覧いただけます。
キーワード | 内容 |
---|---|
ネイティブイメージビルド | Java コードをネイティブマシンコードにコンパイルすることです。通常、Java コードは Java バイトコードと呼ばれる中間言語にコンパイルされ、Java 仮想マシン (JVM) で実行されます。しかし、ネイティブイメージビルドは、Java コードを JVM を介さずに直接実行可能なネイティブマシンコードに変換することで、より高速な実行速度とより低いメモリ使用量を実現することができます。 |
ネイティブイメージビルドは、以下のようなニーズから求められています。
キーワード | 内容 |
---|---|
パフォーマンスの向上 | Java は一般的に高水準のプログラミング言語であり、JVM によって実行されるため、実行速度が遅いとされることがあります。ネイティブイメージビルドにより、高速な実行速度を実現することができます。 |
メモリの最適化 | JVM による Java コードの実行には、多くのメモリが必要となることがあります。ネイティブイメージビルドにより、より少ないメモリ使用量でプログラムを実行できるようになります。 |
ネイティブプログラムの統合 | Java は、C言語や C++ などの他のプログラミング言語で書かれたネイティブプログラムと統合することができます。しかし、統合するためには、ネイティブコードが必要になります。ネイティブイメージビルドにより、これらのネイティブプログラムと Java コードをシームレスに統合することができます。 |
開発環境
- Windows 11 Home 22H2 を使用しています。
WSL の Ubuntu を操作していきますので macOS の方も参考にして頂けます。
WSL (Microsoft Store アプリ版) ※ こちらの関連記事からインストール方法をご確認いただけます
> wsl --version
WSL バージョン: 2.2.4.0
カーネル バージョン: 5.15.153.1-2
WSLg バージョン: 1.0.61
Ubuntu ※ こちらの関連記事からインストール方法をご確認いただけます
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.04 LTS
Release: 24.04
Codename: noble
Java JDK ※ こちらの関連記事からインストール方法をご確認いただけます
$ 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)
この記事では基本的に Ubuntu のターミナルで操作を行います。Vim を使用してコピペする方法を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
GraalVM ビルド環境の構築
GraalVM とは?
GraalVM は、Oracle によって開発された Java 仮想マシンの1つで、Java や Kotlin、Scala などの様々な言語の実行や、ネイティブバイナリへの変換が可能なマルチ言語ランタイム環境です。GraalVM は、通常の Java 仮想マシンよりも高速に実行することができ、また、Java コードをネイティブバイナリに変換することによって、より高速で軽量なアプリケーションを実行できるようになります。また、JavaScript エンジンである Node.js をホストすることもでき、複数の言語の統合において優れた柔軟性を持っています。
GraalVM インストール
Ubuntu で Native Image ビルドを行うためには以下の手順が必要です。
この記事では手動でインストールする方法を紹介しています。
GraalVM を手動で /usr/lib/jvm/ にインストールします。
$ 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 のパスを確認します。
$ ls -la /usr/lib/jvm/
graalvm-ce-java17-linux-amd64
現在インストールされた JVM を確認します。
※ そのまま Enter します。
$ 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 の優先順位を下げる意図があります。
$ 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 の番号を選択します。
$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac
削除する場合と自動モードに戻す場合
※ 削除する場合
$ 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
※ 自動モードに戻す場合
$ sudo update-alternatives --auto java
$ sudo update-alternatives --auto javac
JAVA_HOME を動的に設定しているので Ubuntu に再ログインします。
※ WSL の場合ターミナルを閉じて再度開きます。
バージョンを確認します。
$ 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 のバージョンを確認します。
$ gu --version
GraalVM Updater 23.0.2
GraalVM Updater は、GraalVM のインストール、アップデート、アンインストールを行うためのツールです。 gu は、GraalVM Updater のコマンドです。
Native Image コマンドをインストール(確認)します。
$ 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 のバージョンを確認します。
$ 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)
GraalVM Native Image は、Java アプリケーションをネイティブコードにコンパイルし、高速かつ軽量なネイティブアプリケーションを生成するためのツールです。
ネイティブバイナリへのビルドに必要なライブラリをインストールします。
$ sudo apt update
$ sudo apt install build-essential libz-dev zlib1g-dev
これで GraalVM ビルド環境がインストールされました。
Hello World を表示する手順
プロジェクトの作成
プロジェクトフォルダを作成します。
※ ~/tmp/hello-graalvm をプロジェクトフォルダとします。
$ mkdir -p ~/tmp/hello-graalvm
$ cd ~/tmp/hello-graalvm
Java クラスの作成
Java ソースファイルを作成します。
$ vim HelloWorld.java
ファイルの内容
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
JDK のバージョン確認
GraalVM が選択されているか確認してください。
$ 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 ソースファイルをネイティブマシンコードにコンパイルします。
$ javac HelloWorld.java
$ native-image HelloWorld
詳細な出力を表示します。
$ 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.
ディレクトリ構成
プロジェクトのファイル構成を表示してみます。
$ tree
.
├── HelloWorld.class
├── HelloWorld.java
└── helloworld
$ 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 というネイティブイメージビルドのアプリケーションが作成されています。
アプリ実行
ネイティブイメージを実行します。
$ ./helloworld
Hello World!
ターミナルに "Hello World!" と表示することが出来ました。
まとめ
- Ubuntu に GraalVM をインストールして、Java のネイティブイメージビルドした実行可能ファイルを作成、実行することが出来ました。
この記事は GraalVM をインストールして、Java のネイティブイメージをビルドし、アプリを作成、実行することを目的としています。現時点では通常のビルド方法と比べて、明確なメリットがあるわけではありません。しかしながら、今後の記事では GraalVM の利点や、ネイティブイメージビルドによるアプリの実行速度の向上などを紹介していく予定です。
どうでしたか? Window 11 の WSL Ubuntu に、GraalVM を使用した Java のネイティブイメージビルド環境を手軽に構築することができます。ぜひお試しください。今後も Java の開発環境などを紹介していきますので、ぜひお楽しみにしてください。
参考資料
GraalVM