Native Image(GraalVM)
はじめに
GraalVM Native Imageは、Javaアプリケーションを事前(AOT: Ahead-Of-Time)コンパイルしてネイティブ実行可能ファイルを生成する技術です。起動時間とメモリ使用量を大幅に削減でき、コンテナ環境やサーバーレスに適しています。
JIT vs AOT
┌─────────────────────────────────────────────────────────────────────┐
│ JIT vs AOT コンパイル │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ JIT(Just-In-Time)- 従来のJVM │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ .java → .class → JVM起動 → インタプリタ → JITコンパイル │ │
│ │ ↑ ↓ │ │
│ │ 遅い起動 ピーク性能到達 │ │
│ │ │ │
│ │ 特徴: │ │
│ │ ・起動時間: 秒〜十秒単位 │ │
│ │ ・ピーク性能: 高い(実行時最適化) │ │
│ │ ・メモリ: 多い(JVM + JIT + メタデータ) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ AOT(Ahead-Of-Time)- Native Image │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ .java → .class → native-image → 実行可能ファイル → 即座に実行 │ │
│ │ ↑ ↓ │ │
│ │ ビルド時間長い 起動が超高速 │ │
│ │ │ │
│ │ 特徴: │ │
│ │ ・起動時間: ミリ秒単位 │ │
│ │ ・ピーク性能: JITより低いことがある │ │
│ │ ・メモリ: 少ない(必要最小限) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
比較表
| 項目 | JIT (HotSpot) | AOT (Native Image) |
|---|---|---|
| 起動時間 | 1〜10秒 | 10〜100ミリ秒 |
| ピーク性能 | 高い | やや低い〜同等 |
| メモリ使用量 | 多い(100MB〜) | 少ない(10〜50MB) |
| ビルド時間 | 速い | 遅い(分単位) |
| 動的機能 | 完全サポート | 制限あり |
| デバッグ | 容易 | 困難 |
Native Image のアーキテクチャ
┌─────────────────────────────────────────────────────────────────────┐
│ Native Image ビルドプロセス │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ソースコード │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 静的解析 (Points-to Analysis) │ │
│ │ ・到達可能なコードを特定 │ │
│ │ ・使用されるクラス・メソッドを列挙 │ │
│ │ ・不要なコードを 除外 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ヒープスナップショット │ │
│ │ ・staticイニシャライザを実行 │ │
│ │ ・初期ヒープ状態をイメージに埋め込み │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ AOTコンパイル │ │
│ │ ・全てのコードをネイティブコードに変換 │ │
│ │ ・最適化適用 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ネイティブ実行可能ファイル │
│ ┌─────────────────────────────────────────────────────────── ──┐ │
│ │ ・Substrate VM(軽量ランタイム) │ │
│ │ ・コンパイル済みアプリケーションコード │ │
│ │ ・初期ヒープスナップショット │ │
│ │ ・GC(Serial GC / G1 GC) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
GraalVM のインストール
SDKMAN での インストール
# GraalVM JDK 21 のインストール
sdk install java 21.0.2-graalce
# 使用
sdk use java 21.0.2-graalce
# 確認
java -version
native-image --version
手動インストール
# macOS (Homebrew)
brew install --cask graalvm-jdk
# 環境変数設定
export GRAALVM_HOME=/Library/Java/JavaVirtualMachines/graalvm-jdk-21/Contents/Home
export PATH=$GRAALVM_HOME/bin:$PATH
基本的な使い方
シンプルなアプリケーション
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, Native Image!");
}
}
# コンパイル
javac HelloWorld.java
# Native Image 生成
native-image HelloWorld
# 実行
./helloworld
ビルドオプション
native-image \
# 出力ファイル名
-o myapp \
# メインクラス指定(JARの場合)
-jar myapp.jar \
# ヒープサイズ
-H:MaxHeapSize=256m \
# GC選択
--gc=G1 \
# 静的リンク(musl libc使用時)
--static --libc=musl \
# ビルド時診断
--verbose \
# 最適化レベル
-O3 \
HelloWorld
Spring Boot との統合
Spring Boot 3.x + GraalVM
<!-- pom.xml -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
# Native Image ビルド
./mvnw -Pnative native:compile
# 実行
./target/myapp
Gradle の場合
// build.gradle.kts
plugins {
id("org.graalvm.buildtools.native") version "0.10.0"
}
graalvmNative {
binaries {
named("main") {
imageName.set("myapp")
mainClass.set("com.example.Application")
buildArgs.add("-O3")
buildArgs.add("--gc=G1")
}
}
}
# Native Image ビルド
./gradlew nativeCompile
# 実行
./build/native/nativeCompile/myapp
動的機能の対応
制限される機能
Native Imageは静的解析に基づくため、以下の動的機能に制限があります。
┌─────────────────── ──────────────────────────────────────────────────┐
│ 動的機能の制限 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ リフレクション │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Class.forName("com.example.MyClass") │ │
│ │ → ビルド時にクラス名が分からない │ │
│ │ → 設定ファイルで明示的に指定が必要 │ │
│ └────────────────────────────────────── ──────────────────────────┘ │
│ │
│ 動的プロキシ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Proxy.newProxyInstance(...) │ │
│ │ → プロキシ対象インターフェースの指定が必要 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ JNI │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ネイティブメソッドの呼び出し │ │
│ │ → JNI設定ファイルで指定が必要 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ リソースアクセス │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ getClass().getResourceAsStream("/config.json") │ │
│ │ → リソース設定ファイルで指定が必要 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ シリアライゼーション │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ObjectInputStream / ObjectOutputStream │ │
│ │ → シリアライズ対象クラスの指定が必要 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
設定ファイルによる対応
src/main/resources/META-INF/native-image/
├── reflect-config.json # リフレクション
├── proxy-config.json # 動的プロキシ
├── resource-config.json # リソース
├── jni-config.json # JNI
└── serialization-config.json # シリアライゼーション
reflect-config.json
[
{
"name": "com.example.User",
"allDeclaredConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true
},
{
"name": "com.example.Order",
"methods": [
{ "name": "getId", "parameterTypes": [] },
{ "name": "setId", "parameterTypes": ["java.lang.Long"] }
]
}
]
resource-config.json
{
"resources": {
"includes": [
{ "pattern": "application\\.yml" },
{ "pattern": "templates/.*\\.html" },
{ "pattern": "static/.*" }
]
}
}
トレーシングエージェント
設定ファイルを自動生成するエージェント:
# エージェントを使用してアプリを実行
java -agentlib:native-image-agent=config-output-dir=src/main/resources/META-INF/native-image \
-jar myapp.jar
# テストを実行して設定を収集
./mvnw test -DargLine="-agentlib:native-image-agent=config-merge-dir=src/main/resources/META-INF/native-image"