カテゴリ: Quarkus 更新日: 2026/03/25

Quarkusのパフォーマンスチューニングを徹底解説!JVMモードとネイティブモードの性能比較

JVMモードとネイティブモードの性能比較
JVMモードとネイティブモードの性能比較

先生と生徒の会話形式で理解しよう

生徒

「Quarkusを使っていると、JVMモードとネイティブモードという言葉をよく聞くのですが、具体的に何が違うんですか?」

先生

「Quarkusには、従来のJavaと同じように動くJVMモードと、GraalVMを使ってOS専用のバイナリを作るネイティブモードがあります。それぞれ、起動速度やメモリ消費量、最大スループットといった性能面で大きな違いがあるんですよ。」

生徒

「パフォーマンスチューニングを考えるとき、どちらを選べばいいか迷ってしまいます。比較のポイントを教えてもらえますか?」

先生

「それでは、開発環境や本番のクラウド環境で役立つ、詳しい性能比較と使い分けの基準を見ていきましょう!」

1. QuarkusのJVMモードとネイティブモードの基本概要

1. QuarkusのJVMモードとネイティブモードの基本概要
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. 圧倒的な差が出る!起動速度の比較

2. 圧倒的な差が出る!起動速度の比較
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)の大幅な削減

3. メモリ消費量(RSS)の大幅な削減
3. メモリ消費量(RSS)の大幅な削減

クラウドネイティブな環境において、メモリ消費量はコストに直結します。JVMモードでは、Java VMそのものが消費するメモリに加え、メタスペースやコードキャッシュなどのオーバーヘッドが存在します。

ネイティブモードでは、アプリケーションの動作に必要なコードだけをバイナリに含め、不要なクラスやライブラリをビルド時に削除します。その結果、**メモリフットプリント(RSS:Resident Set Size)**を極限まで小さく抑えることができます。例えば、JVMモードで150MB程度のメモリを必要とするアプリが、ネイティブモードでは20MB以下で動作することもあります。

コンテナのメモリ制限を厳しく設定できるため、同じサーバーリソース内でより多くのインスタンスを動かせるようになります。

4. スループットと長期的なパフォーマンスの罠

4. スループットと長期的なパフォーマンスの罠
4. スループットと長期的なパフォーマンスの罠

ここまでの話を聞くと「常にネイティブモードが最強」と思われがちですが、実は**スループット(単位時間あたりの処理量)**に関してはJVMモードに軍配が上がることが多いです。

JVM(HotSpot VM)は、プログラムの実行中に負荷状況を監視し、頻繁に実行される「ホットスポット」を動的に最適化する**JIT(Just-In-Time)コンパイル**を行います。これにより、長時間稼働し続けるシステムでは、ネイティブモードよりも高度に最適化されたマシンコードが生成され、結果として高いピークパフォーマンスを発揮します。

対して、ネイティブモードはビルド時に最適化を固定してしまうため、実行時の状況に合わせた柔軟なチューニングは行われません。ただし、最近ではPGO(Profile-Guided Optimization)という手法を用いて、ネイティブモードの性能をJVMに近づける技術も進化しています。

5. パフォーマンス計測のためのサンプルコード:計算処理

5. パフォーマンス計測のためのサンプルコード:計算処理
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. ビルド時間と開発者体験のトレードオフ

6. ビルド時間と開発者体験のトレードオフ
6. ビルド時間と開発者体験のトレードオフ

パフォーマンスチューニング以前に知っておくべきは、**ビルド時間**の差です。JVMモードのビルドは一瞬ですが、ネイティブモードのビルドには膨大なCPUとメモリリソースを使い、数分から数十分かかることも珍しくありません。

そのため、開発中はQuarkusの強力な機能である**Dev Mode(ライブリロード)**を活用してJVMモードでサクサク開発し、CI/CDパイプラインや最終的な本番デプロイ時のみネイティブ化するという運用が一般的です。この柔軟性がQuarkusが愛される理由の一つです。

7. 設定ファイルによるチューニングの例

7. 設定ファイルによるチューニングの例
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. どちらのモードを選ぶべきか?判断基準のガイドライン

8. どちらのモードを選ぶべきか?判断基準のガイドライン
8. どちらのモードを選ぶべきか?判断基準のガイドライン

最終的にどちらを選択すべきかは、アプリケーションの用途によって決まります。以下の表を参考に、最適なモードを選んでみてください。

比較項目 JVMモード ネイティブモード
起動時間 普通(数秒) 極速(数ミリ秒)
メモリ使用量 多い 非常に少ない
ピークスループット 最高(JIT最適化あり) 高い(固定最適化)
ビルド時間 非常に短い 非常に長い
主な用途 長時間稼働の常駐サービス サーバーレス、CLI、スケーラブルなコンテナ

このように、**「常に動き続ける基幹システムならJVMモード」**、**「頻繁に起動・終了を繰り返すマイクロサービスならネイティブモード」**というのが、現在主流のチューニング戦略となっています。

カテゴリの一覧へ
新着記事
New1
Quarkus
Quarkus拡張開発をマスター!ビルドプロセスの仕組みと内部構造を徹底解説
New2
Micronaut
Micronautの@Factoryとは?複雑なBean生成を管理するための方法を解説
New3
Quarkus
QuarkusのDIとCDIを完全理解!@Producesでプロデューサーメソッドを使う方法を初心者向けに解説
New4
Java
JavaのStringBufferクラスを徹底解説!スレッド安全な文字列操作の仕組みと使い分け
人気記事
No.1
Java&Spring記事人気No1
Quarkus
Quarkus入門!GitHub ActionsでCI/CDパイプラインを構築して自動ビルドを実現する方法
No.2
Java&Spring記事人気No2
Java
Javaのコンパイルと実行の流れを解説!JVM・JDK・JREの違いも初心者向けに整理
No.3
Java&Spring記事人気No3
Micronaut
Micronautのルーティング設定ガイド!プレフィックス付与とAPIバージョニングの基本
No.4
Java&Spring記事人気No4
Micronaut
Micronautのフィルタ徹底解説!HTTPリクエスト共通処理をスマートに追加する方法
No.5
Java&Spring記事人気No5
Quarkus
QuarkusのCI/CD入門!GitHub Actionsで自動デプロイを実現する方法
No.6
Java&Spring記事人気No6
Java
Java Functionインタフェースの使い方を完全ガイド!map変換と処理チェーンを理解する
No.7
Java&Spring記事人気No7
Java
JavaのString比較を徹底解説!equalsと==の違い、初心者が陥る罠とは?
No.8
Java&Spring記事人気No8
Quarkus
Quarkus拡張開発を徹底解説!仕組みから自作エクステンションの作り方まで