Java仮想マシン性能チューニングの核心概念と実践ガイド

JVMメモリ構造の全体像

Javaアプリケーションのパフォーマンスを改善するには、まずJVMがどのようにメモリを管理しているかを理解する必要がある。JVMのメモリ領域は大きく分けて「ヒープ」「スタック」「メソッドエリア」「プログラムカウンタ」「ネイティブメソッドスタック」の5つに分かれる。特にチューニングの主戦場となるヒープ領域は、Young Generation(Eden + Survivor0/1)Old Generationに分割され、デフォルトで1:2の比率でサイズが決まる。

クラスローディングの仕組み

ローダーの階層

クラスローダーは三層構造を取り、下位層から上位層へと委譲される。

// ローダーの実体を確認するサンプルコード
public class LoaderHierarchy {
    public static void main(String[] args) {
        ClassLoader app = LoaderHierarchy.class.getClassLoader();
        ClassLoader ext  = app.getParent();
        ClassLoader boot = ext == null ? null : ext.getParent();
        System.out.println("Application: " + app);
        System.out.println("Extension  : " + ext);
        System.out.println("Bootstrap  : " + boot);
    }
}

委譲モデルの実装

loadClassメソッドは以下の流れで動作する。

  1. 既にロード済みかをfindLoadedClassでチェック
  2. 親ローダーに処理を委譲(parent.loadClass)
  3. 親が見つからなければ自身でfindClassで検索
  4. 見つかったらdefineClassでリンク

この仕組みにより、java.lang.Stringなどのコアクラスが意図せず書き換えられるリスクを回避できる。

ロード・リンク・初期化の3ステップ

  • Loading:バイトコードをメモリに読み込み、Classオブジェクトを生成
  • Linking
    • Verification:バイトコードの正当性検証
    • Preparation:staticフィールドにデフォルト値を設定
    • Resolution:シンボリック参照を直接参照に変換
  • Initialization:staticブロックとstaticフィールドの明示的な値を実行

オブジェクトのライフサイクル

オブジェクトは以下のステップで生死を繰り返す。

  1. Eden領域に割り当てられる(Age=0)
  2. Minor GCで生存すればSurvivorへ移動しAgeをインクリメント
  3. Ageが閾値(多くは15)に達するとOld Generationへ昇格
  4. Old Generationが満杯になるとFull GCが発生
  5. 到達不能と判定されたオブジェクトが回収される

ガベージコレクションの判定アルゴリズム

根集合(GC Root)からの到達可能性

現在のJVMでは「Reference Counting」は採用されておらず、Root Tracing方式が使われている。Rootとなるオブジェクトは以下の通り。

  • 各スレッドのスタックフレーム内のローカル変数
  • staticフィールドに保持されている参照
  • JNIハンドル
  • 実行中のメソッドが保持する参照

主要なGCアルゴリズムと特徴

アルゴリズム概要メリットデメリット
Mark-Sweepマーク→スイープの2段階実装がシンプルメモリ断片化が発生
Copying使用領域を半分に分割し、生存オブジェクトをコピー断片化しないメモリ効率が悪い
Mark-Compact生存オブジェクトを片側へ集約してからクリア断片化もメモリ効率も良好コストが高い

代表的なガベージコレクタ

  • Serial / Serial Old:シングルスレッドで動作。小規模アプリ向き。
  • ParNew:Serialのマルチスレッド版。CMSと併用される。
  • Parallel Scavenge / Parallel Old:スループット重視。バッチ処理に最適。
  • CMS(Concurrent Mark-Sweep):低レイテンシを実現するが、フレージメント問題あり。
  • G1(Garbage First):リージョン単位で管理し、予測可能な一時停止時間を提供。

Stop-The-World(STW)イベント

GC実行中にアプリケーションスレッドを一時停止させる現象。CMS/G1では以下のフェーズで発生する。

  • Initial Mark:Root集合をスキャン
  • Remark:並行マーク中の変更点を再スキャン
  • Evacuation Pause(G1のみ):リージョンの再配置

三色マーキング

並行マークフェーズで使用される概念。

  • White:未走査
  • Gray:自身は走査済み、参照先は未走査
  • Black:自身と参照先ともに走査済み

このモデルにより、マークフェーズとアプリケーションフェーズが同時に進行できる。

チューニング実践

JVM起動オプションの分類

  • 標準オプション:-option(例:-server)
  • 非標準オプション:-Xoption(例:-Xmx2g)
  • 実験的オプション:-XX:option(例:-XX:+UseG1GC)
# 実際に有効になっているフラグを確認
java -XX:+PrintFlagsFinal -version | grep UseG1GC

診断ツールの活用

本番環境でのボトルネック調査に役立つツールを以下に示す。

  • Arthas:リアルタイムでメソッド実行時間やヒープダンプ取得が可能
  • VisualVM:GUIベースでヒープやスレッドの可視化
  • Java Flight Recorder:低オーバーヘッドで詳細なプロファイルを収集
# ArthasでCPU最上位メソッドを特定
dashboard -i 2000
profiler start --event cpu
profiler stop --file /tmp/cpu.html

パフォーマンス改善の指針

  1. GCログを収集し、一時停止時間と回収効率を把握
  2. ヒープダンプを取得し、メモリリーク箇所を特定
  3. オブジェクト割り当て率が高いメソッドを特定し、キャッシュやプールを導入
  4. CMSやG1のパラメータを調整し、フルGC頻度を削減
  5. アプリケーション要件に応じてGCアルゴリズムを選択(レスポンスタイム vs スループット)

タグ: JVM GC HotSpot G1 CMS

6月24日 01:05 投稿