Quarkusのパフォーマンスチューニングを徹底解説!JVMモードとネイティブモードの性能比較
生徒
「Quarkusを使っていると、JVMモードとネイティブモードという言葉をよく聞くのですが、具体的に何が違うんですか?」
先生
「Quarkusには、従来のJavaと同じように動くJVMモードと、GraalVMを使ってOS専用のバイナリを作るネイティブモードがあります。それぞれ、起動速度やメモリ消費量、最大スループットといった性能面で大きな違いがあるんですよ。」
生徒
「パフォーマンスチューニングを考えるとき、どちらを選べばいいか迷ってしまいます。比較のポイントを教えてもらえますか?」
先生
「それでは、開発環境や本番のクラウド環境で役立つ、詳しい性能比較と使い分けの基準を見ていきましょう!」
1. QuarkusのJVMモードとネイティブモードの基本概要
Quarkus(クォーカス)は、Kubernetes Native Javaスタックとして設計されたモダンなフレームワークです。最大の特徴は、実行環境に合わせて**JVMモード(Java Virtual Machine)**と**ネイティブモード(Native Executable)**を自由に選択できる点にあります。
JVMモードは、従来のJava開発と同様にJARファイルを作成し、Javaランタイム上で動作させます。一方、ネイティブモードはGraalVM(グラールVM)の**AOTコンパイル(Ahead-of-Time compilation)**技術を利用して、特定のOSで直接実行可能なバイナリを生成します。この違いが、アプリケーションの起動時間やメモリ効率に劇的な変化をもたらします。
2. 圧倒的な差が出る!起動速度の比較
Quarkusのネイティブモードが最も輝くのは、**「起動速度」**です。従来のJavaアプリケーションは、起動時にクラスパスのスキャンや動的なクラスロード、初期化処理を大量に行うため、数秒から数十秒かかるのが一般的でした。
しかし、Quarkusはビルド時にこれらの処理を可能な限り済ませてしまいます。ネイティブモードでは、メモリ上のイメージをそのままロードするだけで済むため、ミリ秒単位での起動が可能になります。これは、サーバーレス(AWS Lambdaなど)や、負荷に応じて瞬時にスケールアウトが必要なコンテナ環境で非常に強力な武器となります。
まずは、簡単なREST APIのエンドポイントを定義するコードを見てみましょう。
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus!";
}
}
このアプリケーションを各モードで起動した際のログ出力結果のイメージを比較してみましょう。
JVMモードでの起動ログ:
INFO [io.quarkus] (main) Quarkus 3.x started in 1.250s. Listening on: http://0.0.0.0:8080
ネイティブモードでの起動ログ:
INFO [io.quarkus] (main) Quarkus 3.x started in 0.015s. Listening on: http://0.0.0.0:8080
このように、ネイティブモードはJVMモードに比べて数十倍から百倍近い速さで起動することがわかります。
3. メモリ消費量(RSS)の大幅な削減
クラウドネイティブな環境において、メモリ消費量はコストに直結します。JVMモードでは、Java VMそのものが消費するメモリに加え、メタスペースやコードキャッシュなどのオーバーヘッドが存在します。
ネイティブモードでは、アプリケーションの動作に必要なコードだけをバイナリに含め、不要なクラスやライブラリをビルド時に削除します。その結果、**メモリフットプリント(RSS:Resident Set Size)**を極限まで小さく抑えることができます。例えば、JVMモードで150MB程度のメモリを必要とするアプリが、ネイティブモードでは20MB以下で動作することもあります。
コンテナのメモリ制限を厳しく設定できるため、同じサーバーリソース内でより多くのインスタンスを動かせるようになります。
4. スループットと長期的なパフォーマンスの罠
ここまでの話を聞くと「常にネイティブモードが最強」と思われがちですが、実は**スループット(単位時間あたりの処理量)**に関してはJVMモードに軍配が上がることが多いです。
JVM(HotSpot VM)は、プログラムの実行中に負荷状況を監視し、頻繁に実行される「ホットスポット」を動的に最適化する**JIT(Just-In-Time)コンパイル**を行います。これにより、長時間稼働し続けるシステムでは、ネイティブモードよりも高度に最適化されたマシンコードが生成され、結果として高いピークパフォーマンスを発揮します。
対して、ネイティブモードはビルド時に最適化を固定してしまうため、実行時の状況に合わせた柔軟なチューニングは行われません。ただし、最近ではPGO(Profile-Guided Optimization)という手法を用いて、ネイティブモードの性能をJVMに近づける技術も進化しています。
5. パフォーマンス計測のためのサンプルコード:計算処理
実行性能の違いを体感するために、負荷のかかる計算処理を行うサービスを考えてみます。複雑なロジックを回す場合、JITコンパイラの最適化能力が重要になります。
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CalculationService {
public long performHeavyLoop(int iterations) {
long result = 0;
for (int i = 0; i < iterations; i++) {
result += (long) Math.sqrt(i) * (i % 10);
}
return result;
}
}
このサービスを呼び出すJAX-RSリソースの例です。
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
@Path("/calc")
public class CalcResource {
@Inject
CalculationService service;
@GET
public String calculate(@QueryParam("n") int n) {
long start = System.currentTimeMillis();
long val = service.performHeavyLoop(n);
long end = System.currentTimeMillis();
return "Result: " + val + " (Time: " + (end - start) + "ms)";
}
}
長時間連続でリクエストを送り続けると、JVMモードでは徐々にコンパイル最適化が進み、処理時間が短縮される傾向が見られます。一方でネイティブモードは最初から最後まで安定した速度で動作します。
6. ビルド時間と開発者体験のトレードオフ
パフォーマンスチューニング以前に知っておくべきは、**ビルド時間**の差です。JVMモードのビルドは一瞬ですが、ネイティブモードのビルドには膨大なCPUとメモリリソースを使い、数分から数十分かかることも珍しくありません。
そのため、開発中はQuarkusの強力な機能である**Dev Mode(ライブリロード)**を活用してJVMモードでサクサク開発し、CI/CDパイプラインや最終的な本番デプロイ時のみネイティブ化するという運用が一般的です。この柔軟性がQuarkusが愛される理由の一つです。
7. 設定ファイルによるチューニングの例
JVMモード、ネイティブモードどちらであっても、Quarkusの動作は application.properties で細かく制御できます。例えば、ビルド時の最適化設定を調整することで、さらにパフォーマンスを向上させることが可能です。
# アプリケーションの基本設定
quarkus.application.name=performance-demo
# JVMモードでのヒープメモリ最適化設定
quarkus.hibernate-orm.database.generation=none
# ネイティブビルド時の最適化設定(リフレクションの削減など)
quarkus.arc.remove-unused-beans=true
# スレッドプールの調整
quarkus.thread-pool.max-threads=100
これらを設定することで、ランタイムの負荷を減らし、応答性能を安定させることができます。特に大規模なアプリケーションでは、未使用のBeanを削除する設定などがメモリ節約に大きく貢献します。
8. どちらのモードを選ぶべきか?判断基準のガイドライン
最終的にどちらを選択すべきかは、アプリケーションの用途によって決まります。以下の表を参考に、最適なモードを選んでみてください。
| 比較項目 | JVMモード | ネイティブモード |
|---|---|---|
| 起動時間 | 普通(数秒) | 極速(数ミリ秒) |
| メモリ使用量 | 多い | 非常に少ない |
| ピークスループット | 最高(JIT最適化あり) | 高い(固定最適化) |
| ビルド時間 | 非常に短い | 非常に長い |
| 主な用途 | 長時間稼働の常駐サービス | サーバーレス、CLI、スケーラブルなコンテナ |
このように、**「常に動き続ける基幹システムならJVMモード」**、**「頻繁に起動・終了を繰り返すマイクロサービスならネイティブモード」**というのが、現在主流のチューニング戦略となっています。