JVMをウォームアップする方法

JVMをウォームアップする方法

1. 概要

JVMは、これまでに構築された中で最も古く強力な仮想マシンの1つです。

この記事では、JVMをウォームアップすることの意味とその方法を簡単に説明します。

2. JVMアーキテクチャの基本

新しいJVMプロセスが開始されるたびに、必要なすべてのクラスがClassLoaderのインスタンスによってメモリにロードされます。 このプロセスは3つのステップで行われます。

  1. Bootstrap Class Loading:Bootstrap Class Loader」は、Javaコードおよびjava.lang.Objectなどの必須のJavaクラスをメモリにロードします。 これらのロードされたクラスはJRE\lib t.jarにあります。

  2. Extension Class LoadingExtClassLoaderは、java.ext.dirsパスにあるすべてのJARファイルをロードする役割を果たします。 開発者が手動でJARを追加する非Mavenまたは非Gradleベースのアプリケーションでは、これらのクラスはすべてこのフェーズ中にロードされます。

  3. Application Class LoadingAppClassLoaderは、アプリケーションクラスパスにあるすべてのクラスをロードします。

この初期化プロセスは、遅延読み込み方式に基づいています。

3. JVMのウォーミングアップとは

クラスのロードが完了すると、(プロセスの開始時に使用される)すべての重要なクラスがJVM cache (native code)にプッシュされます。これにより、実行時にそれらにすばやくアクセスできるようになります。 他のクラスはリクエストごとにロードされます。

Java Webアプリケーションに対する最初の要求は、プロセスのライフタイム中の平均応答時間よりも大幅に遅いことがよくあります。 このウォームアップ期間は、通常、遅延クラスのロードとジャストインタイムのコンパイルに起因します。

このことを念頭に置いて、低レイテンシのアプリケーションでは、すべてのクラスを事前にキャッシュして、実行時にアクセスしたときにすぐに利用できるようにする必要があります。

JVMを調整するこのプロセスは、ウォーミングアップと呼ばれます。

4. 階層型コンパイル

JVMの健全なアーキテクチャのおかげで、頻繁に使用されるメソッドは、アプリケーションのライフサイクル中にネイティブキャッシュにロードされます。

このプロパティを使用して、アプリケーションの起動時に重要なメソッドを強制的にキャッシュにロードできます。 その範囲で、Tiered Compilation:という名前のVM引数を設定する必要があります

-XX:CompileThreshold -XX:TieredCompilation

通常、VMはインタープリターを使用して、コンパイラーに供給されるメソッドに関するプロファイリング情報を収集します。 階層型スキームでは、インタープリターに加えて、クライアントコンパイラを使用して、自身のプロファイリング情報を収集するメソッドのコンパイル済みバージョンを生成します。

コンパイルされたコードは、解釈されたコードよりもかなり高速であるため、プログラムはプロファイリング段階でより高いパフォーマンスで実行されます。

このVM引数が有効になっているJBossおよびJDKバージョン7で実行されているアプリケーションは、文書化されたbugが原因で、しばらくするとクラッシュする傾向があります。 この問題はJDKバージョン8で修正されました。

ここで注意すべきもう1つのポイントは、ロードを強制するために、実行されるすべての(またはほとんどの)クラスにアクセスする必要があることを確認する必要があるということです。 これは、単体テスト中にコードカバレッジを決定することに似ています。 より多くのコードがカバーされるほど、パフォーマンスは向上します。

次のセクションでは、これを実装する方法を示します。

5. 手動実装

JVMをウォームアップする代替手法を実装する場合があります。 この場合、単純な手動のウォームアップには、アプリケーションの起動直後に異なるクラスの作成を数千回繰り返すことが含まれます。

まず、通常のメソッドでダミークラスを作成する必要があります。

public class Dummy {
    public void m() {
    }
}

次に、アプリケーションが起動するとすぐに少なくとも100,000回実行される静的メソッドを持つクラスを作成する必要があります。実行ごとに、前述のダミークラスの新しいインスタンスが作成されます。

public class ManualClassLoader {
    protected static void load() {
        for (int i = 0; i < 100000; i++) {
            Dummy dummy = new Dummy();
            dummy.m();
        }
    }
}

ここで、measure the performance gainを実行するには、メインクラスを作成する必要があります。 このクラスには、ManualClassLoader’s load()メソッドへの直接呼び出しを含む静的ブロックが1つ含まれています。

main関数内で、ManualClassLoader’s load()メソッドをもう一度呼び出し、関数呼び出しの直前と直後のシステム時間をナノ秒単位でキャプチャします。 最後に、これらの時間を減算して、実際の実行時間を取得します。

アプリケーションを2回実行する必要があります。 1回は静的ブロック内のload()メソッド呼び出しを使用し、もう1回はこのメソッド呼び出しを使用しません。

public class MainApplication {
    static {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Warm Up time : " + (end - start));
    }
    public static void main(String[] args) {
        long start = System.nanoTime();
        ManualClassLoader.load();
        long end = System.nanoTime();
        System.out.println("Total time taken : " + (end - start));
    }
}

以下の結果はナノ秒単位で再現されています。

ウォームアップあり

ウォームアップなし

差(%)

1220056

8903640

730

1083797

13609530

1256

1026025

9283837

905

1024047

7234871

706

868782

9146180

1053

予想どおり、ウォームアップアプローチを使用すると、通常のアプローチよりもはるかに優れたパフォーマンスを示します。

もちろん、これはvery simplistic benchmarkであり、この手法の影響に関する表面レベルの洞察を提供するだけです。 また、実際のアプリケーションでは、システム内の一般的なコードパスでウォームアップする必要があることを理解することが重要です。

6. 道具

また、いくつかのツールを使用してJVMをウォームアップすることもできます。 最もよく知られているツールの1つは、Java Microbenchmark Harness、JMHです。 通常、マイクロベンチマークに使用されます。 ロードされると、it repeatedly hits a code snippet and monitors the warm-up iteration cycle.

これを使用するには、pom.xmlに別の依存関係を追加する必要があります。


    org.openjdk.jmh
    jmh-core
    1.19


    org.openjdk.jmh
    jmh-generator-annprocess
    1.19

JMHの最新バージョンはCentral Maven Repositoryで確認できます。

または、JMHのMavenプラグインを使用してサンプルプロジェクトを生成することもできます。

mvn archetype:generate \
    -DinteractiveMode=false \
    -DarchetypeGroupId=org.openjdk.jmh \
    -DarchetypeArtifactId=jmh-java-benchmark-archetype \
    -DgroupId=com.example \
    -DartifactId=test \
    -Dversion=1.0

次に、mainメソッドを作成しましょう。

public static void main(String[] args)
  throws RunnerException, IOException {
    Main.main(args);
}

次に、メソッドを作成し、JMHの@Benchmarkアノテーションでアノテーションを付ける必要があります。

@Benchmark
public void init() {
    //code snippet
}

このinitメソッド内で、ウォームアップするために繰り返し実行する必要のあるコードを記述する必要があります。

7. パフォーマンスベンチマーク

過去20年間、Javaへのほとんどの貢献はGC(ガベージコレクター)とJIT(Just In Time Compiler)に関連していました。 オンラインで見つかったパフォーマンスベンチマークのほぼすべては、すでにしばらく実行されているJVMで実行されます。 しかしながら、

ただし、Beihang Universityは、JVMのウォームアップ時間を考慮したベンチマークレポートを公開しています。 HadoopおよびSparkベースのシステムを使用して、大量のデータを処理しました。

image

ここで、HotTubはJVMがウォームアップされた環境を指定します。

ご覧のとおり、特に比較的小さな読み取り操作の場合、高速化が大幅に向上する可能性があります。このため、このデータを検討するのは興味深いことです。

8. 結論

この簡単な記事では、アプリケーションの起動時にJVMがクラスを読み込む方法と、パフォーマンスを向上させるためにJVMをウォームアップする方法を示しました。

続行する場合は、This bookでトピックに関する詳細情報とガイドラインを確認してください。

そして、いつものように、完全なソースコードはover on GitHubで利用できます。