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

QuarkusのGCチューニング入門!Javaアプリのパフォーマンスを最適化する基本技術

QuarkusアプリのGCチューニング基本
QuarkusアプリのGCチューニング基本

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

生徒

「最近Quarkus(クオーカス)を使い始めたのですが、ガベージコレクション(GC)のチューニングって必要なんですか?ネイティブモードなら不要だと思っていました。」

先生

「それは重要な視点ですね!Quarkusは確かに軽量で高速ですが、JVMモードで動かす場合や、ネイティブ実行ファイルでもメモリ管理の仕組みは存在します。特に大規模なリクエストを処理する際は、GCの動作を理解して適切に設定することが、安定稼働への近道ですよ。」

生徒

「なるほど。具体的にどんな設定をすれば、メモリの無駄を省いてパフォーマンスを上げられるのでしょうか?」

先生

「まずは基本となるJavaのメモリ構造と、Quarkus特有の最適化設定について順番に学んでいきましょう!」

1. Quarkusにおけるガベージコレクションの役割

1. Quarkusにおけるガベージコレクションの役割
1. Quarkusにおけるガベージコレクションの役割

Java仮想マシン(JVM)上で動作するQuarkusアプリケーションにとって、ガベージコレクション(GC)は「使い終わったメモリを自動的に回収する仕組み」です。開発者が手動でメモリを解放する必要がないため非常に便利ですが、GCが実行されている間はアプリケーションの処理が一時的に止まる「Stop The World(STW)」という現象が発生します。

クラウドネイティブな環境やマイクロサービスでは、レスポンスタイムの短縮が求められます。GCの頻度が多かったり、一回の回収に時間がかかりすぎたりすると、システム全体のパフォーマンスが低下してしまいます。そのため、Quarkusのポテンシャルを最大限に引き出すためには、適切なGCアルゴリズムの選択とパラメータ調整が欠かせません。

2. JVMモードでのGCアルゴリズムの選択肢

2. JVMモードでのGCアルゴリズムの選択肢
2. JVMモードでのGCアルゴリズムの選択肢

Quarkusを標準的なJVMモードで実行する場合、使用するJDKのバージョンによってデフォルトのGCアルゴリズムが異なります。一般的には以下の三つのアルゴリズムがよく検討されます。

  • G1GC (Garbage First Garbage Collector): Java 9以降のデフォルトです。大容量メモリでも停止時間を予測可能に抑える設計になっています。
  • ZGC (Z Garbage Collector): 超低遅延を目的としたGCで、テラバイト級のメモリでも停止時間をミリ秒以下に抑えることができます。
  • Shenandoah GC: ZGCと同様に低遅延を目指したアルゴリズムで、Red Hat社が主導して開発しているため、Quarkusとの相性も抜群です。

以下のコードは、Quarkusを起動する際に特定のGCアルゴリズムを指定する例です。


// 起動時に環境変数や引数でGCを指定するイメージ
// 実際にはDockerfileやjava -jarコマンドの引数として渡します
public class GcConfigExample {
    public static void main(String[] args) {
        System.out.println("Applying Shenandoah GC for Low Latency...");
        // 実行時の引数例: -XX:+UseShenandoahGC
    }
}

3. コンテナ環境でのメモリ制限とHeapSizeの設定

3. コンテナ環境でのメモリ制限とHeapSizeの設定
3. コンテナ環境でのメモリ制限とHeapSizeの設定

QuarkusはDockerやKubernetesなどのコンテナ環境で動かすことが一般的です。コンテナにはメモリ制限(Limit)がありますが、JVMがその制限を正しく認識できないと、コンテナが強制終了(OOM Kill)されてしまうことがあります。

Quarkusでは、application.propertiesに設定を書くのではなく、JVMの起動オプションでヒープサイズを制御するのが基本です。特に、コンテナのメモリ使用率に合わせて動的にヒープサイズを決定する設定が推奨されます。


// メモリ使用量を制限するためのオプション設定例
public class MemoryLimitSettings {
    public static void main(String[] args) {
        // コンテナのメモリの50%をヒープに割り当てる設定
        // 引数例: -XX:MaxRAMPercentage=50.0
        System.out.println("Max Heap is set to 50% of Container Memory.");
    }
}

4. ネイティブイメージにおけるGCの仕組み

4. ネイティブイメージにおけるGCの仕組み
4. ネイティブイメージにおけるGCの仕組み

Quarkusの最大の特徴である「Native Executable(ネイティブ実行ファイル)」では、通常のJVMとは異なる「GraalVM Native Image GC」が動作します。デフォルトではSerial GCという、非常にシンプルで省メモリなアルゴリズムが採用されています。

Serial GCは単一スレッドで動作するため、メモリ消費量は極めて少ないですが、スループットが高いアプリケーションではボトルネックになる可能性があります。企業向けの商用版(MandrelやGraalVM Enterprise)では、ネイティブイメージでもG1GCを使用することが可能です。

ネイティブビルド時にGCを指定する場合は、ビルドオプションに追加します。以下はMavenプロジェクトでの設定イメージです。


<!-- pom.xmlのプロパティ設定例 -->
<properties>
    <quarkus.native.additional-build-args>
        --gc=G1
    </quarkus.native.additional-build-args>
</properties>

5. オブジェクトの生存期間を意識したコーディング

5. オブジェクトの生存期間を意識したコーディング
5. オブジェクトの生存期間を意識したコーディング

チューニングにおいて最も効果的なのは、実は「GCに仕事をさせないこと」です。不要なオブジェクト生成を減らすことで、GCの頻度を劇的に下げることができます。特に、文字列結合を繰り返す処理や、大きなコレクションのコピーには注意が必要です。

Quarkusの依存注入(CDI)を利用する際も、スコープを適切に設定しましょう。例えば、状態を持たないサービスであれば@ApplicationScopedを使うことで、リクエストごとにインスタンスが生成されるのを防ぐことができます。


import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class PerformanceService {
    public String processData(String input) {
        // StringBuilderを使用してメモリ効率を高める
        StringBuilder sb = new StringBuilder();
        sb.append("Processed: ").append(input);
        return sb.toString();
    }
}

6. メモリリークを防ぐためのモニタリング手法

6. メモリリークを防ぐためのモニタリング手法
6. メモリリークを防ぐためのモニタリング手法

どれだけGC設定を煮詰めても、アプリケーションコードにメモリリークがあれば、いずれシステムはダウンします。Quarkusには標準でMicrometer Metricsという拡張機能があり、これを利用することでリアルタイムにヒープの使用状況を監視できます。

PrometheusやGrafanaと組み合わせることで、GCの実行回数や停止時間を可視化し、異常なメモリ消費の予兆をいち早く察知することが可能になります。開発段階からメトリクスを確認する癖をつけることが、高いパフォーマンスを維持するコツです。


# Micrometerが出力するメトリクスの例
jvm_memory_used_bytes{area="heap",id="G1 Old Gen"} 125829120
jvm_gc_pause_seconds_count 42
jvm_gc_pause_seconds_sum 0.845

7. Quarkus特有のビルド時最適化の恩恵

7. Quarkus特有のビルド時最適化の恩恵
7. Quarkus特有のビルド時最適化の恩恵

Quarkusが他のJavaフレームワークより高速な理由の一つに、ビルド時にメタデータの解析を済ませてしまう「Build Time Boot」があります。これにより、実行時のクラスロードやリフレクションが大幅に削減されます。

これはGCにも好影響を与えます。起動時に生成される一時的なオブジェクトが少なくなるため、アプリケーションが安定状態(ウォームアップ完了後)に移行するまでの時間が短縮されます。私たちは、フレームワークが提供するこの仕組みを壊さないよう、極力軽量なライブラリ選定を心がけるべきです。

8. パフォーマンス検証のための負荷テストの重要性

8. パフォーマンス検証のための負荷テストの重要性
8. パフォーマンス検証のための負荷テストの重要性

最後のステップは、実際の環境を模した負荷テストです。理論上の設定が常に最適とは限りません。JMeterやGatlingといったツールを使用して、秒間リクエスト数を増やしたときにGCがどのように振る舞うかを確認します。

特に、ヒープメモリがいっぱいになったときに発生する「Full GC」が発生していないかをチェックしてください。もし頻発しているようであれば、ヒープサイズの増量か、アルゴリズムの再選定、あるいはコード内での大きなオブジェクト生成の見直しが必要です。実測値に基づいたチューニングこそが、最も信頼できるパフォーマンス向上策となります。

カテゴリの一覧へ
新着記事
New1
Micronaut
開発環境構築でつまずきやすいポイントまとめ!Micronautのエラーと対処法一覧
New2
Quarkus
QuarkusとGraalVMの進化を徹底解説!Javaネイティブイメージが切り拓く次世代開発の将来展望
New3
Micronaut
Micronaut + IntelliJ のプラグイン活用術!自動補完・テンプレート便利機能紹介
New4
Quarkus
QuarkusとGitHub Codespacesでクラウド開発を始める方法を完全解説!初心者でも迷わない開発環境構築ガイド
人気記事
No.1
Java&Spring記事人気No1
Quarkus
Quarkus拡張開発を徹底解説!仕組みから自作エクステンションの作り方まで
No.2
Java&Spring記事人気No2
Java
Javaの配列とは?基本の使い方・宣言・初期化を初心者向けにわかりやすく解説
No.3
Java&Spring記事人気No3
Java
Java ArrayListの使い方完全ガイド|追加・削除・取得・検索の基本操作
No.4
Java&Spring記事人気No4
Micronaut
MicronautのHTTPサーバー性能を最大化!初心者でもできるパフォーマンスチューニング
No.5
Java&Spring記事人気No5
Java
Javaの型変換(キャスト)を徹底解説!暗黙的・明示的変換の違いを整理
No.6
Java&Spring記事人気No6
Java
Javaの配列検索を完全攻略!値の探し方や多次元配列の条件一致を解説
No.7
Java&Spring記事人気No7
Java
Javaのコンパイルと実行の流れを解説!JVM・JDK・JREの違いも初心者向けに整理
No.8
Java&Spring記事人気No8
Quarkus
Quarkus入門!GitHub ActionsでCI/CDパイプラインを構築して自動ビルドを実現する方法