1. 序章

このチュートリアルでは、Java仮想マシンでのインライン化の方法とその機能について説明します。

また、JVMからのインライン化に関連する情報を取得して読み取る方法と、コードを最適化するためにこの情報を使用して何ができるかについても説明します。

2. インライン化の方法は何ですか?

基本的に、インライン化は、最も頻繁に実行されるメソッドの呼び出しをその本体に置き換えることにより、実行時にコンパイルされたソースコードを最適化する方法です。

コンパイルは含まれますが、従来の javac コンパイラではなく、JVM自体によって実行されます。 より正確には、はJVMの一部であるJust-In-Time(JIT)コンパイラの責任です。 javac はバイトコードのみを生成し、JITに魔法をかけてソースコードを最適化します。

このアプローチの最も重要な結果の1つは、古いJavaを使用してコードをコンパイルすると、同じ。classファイルが新しいJVMで高速になることです。 このように、ソースコードを再コンパイルする必要はなく、Javaを更新するだけです。

3. JITはどのようにそれを行いますか?

基本的に、 JITコンパイラは、メソッド呼び出しのオーバーヘッドを回避できるように、頻繁に呼び出すメソッドをインライン化しようとします。 メソッドをインライン化するかどうかを決定する際には、2つのことを考慮に入れます。

まず、カウンターを使用して、メソッドを呼び出した回数を追跡します。 メソッドが特定の回数以上呼び出されると、「ホット」になります。 このしきい値はデフォルトで10,000に設定されていますが、Javaの起動時にJVMフラグを使用して構成できます。 時間がかかり、巨大なバイトコードが生成されるため、すべてをインライン化する必要はありません。

インライン化は、安定した状態になったときにのみ行われることに注意してください。 これは、JITコンパイラに十分なプロファイリング情報を提供するために、実行を数回繰り返す必要があることを意味します。

さらに、「ホット」であることは、メソッドがインライン化されることを保証するものではありません。 大きすぎると、JITはインライン化しません。 許容サイズは、 -XX:FreqInlineSize = フラグによって制限されます。このフラグは、メソッドにインライン化するバイトコード命令の最大数を指定します。

それでも、このフラグがどのような影響を与える可能性があるかを完全に確信している場合を除いて、このフラグのデフォルト値を変更しないことを強くお勧めします。 デフォルト値はプラットフォームによって異なります– 64ビットLinuxの場合、325です。

JITは、静的、プライベート 、または一般的なfinalメソッドをインライン化します。 そして、 パブリックメソッドもインライン化の候補であり、すべてのパブリックメソッドが必ずしもインライン化されるわけではありません。 JVMは、そのようなメソッドの実装が1つしかないことを判別する必要があります 。 サブクラスを追加すると、インライン化が妨げられ、パフォーマンスが必然的に低下します。

4. ホットメソッドを見つける

確かに、JITが何をしているのか推測したくありません。 したがって、どのメソッドがインライン化されているか、またはインライン化されていないかを確認する方法が必要です。 起動時にいくつかの追加のJVMフラグを設定することで、これを簡単に実現し、このすべての情報を標準出力に記録できます。

-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining

最初のフラグは、JITコンパイルが発生したときにログに記録されます。 2番目のフラグは、 -XX:+ PrintInlining を含む追加のフラグを有効にします。これにより、インライン化されるメソッドと場所が出力されます。

これにより、インライン化されたメソッドがツリーの形で表示されます。 葉には注釈が付けられ、次のオプションのいずれかでマークが付けられます。

  • インライン(ホット) –このメソッドはホットとしてマークされ、インライン化されます
  • too big –メソッドはホットではありませんが、生成されたバイトコードが大きすぎるため、インライン化されていません
  • ホットメソッドが大きすぎます–これはホットメソッドですが、バイトコードが大きすぎるためインライン化されません

3番目の値に注意を払い、「ホットメソッドが大きすぎます」というラベルの付いたメソッドを最適化するようにしてください。

一般に、非常に複雑な条件ステートメントを含むホットメソッドを見つけた場合は、 if- ステートメントのコンテンツを分離し、粒度を上げてJITがコードを最適化できるようにする必要があります。 switchおよびfor-loopステートメントについても同じことが言えます。

手動によるメソッドのインライン化は、コードを最適化するために行う必要がないことであると結論付けることができます。 JVMはそれをより効率的に実行し、コードを長くして追跡しにくくする可能性があります。

4.1. 例

これを実際に確認する方法を見てみましょう。 最初に、最初のNの連続する正の整数の合計を計算する単純なクラスを作成します。

public class ConsecutiveNumbersSum {

    private long totalSum;
    private int totalNumbers;

    public ConsecutiveNumbersSum(int totalNumbers) {
        this.totalNumbers = totalNumbers;
    }

    public long getTotalSum() {
        totalSum = 0;
        for (int i = 0; i < totalNumbers; i++) {
            totalSum += i;
        }
        return totalSum;
    }
}

次に、簡単なメソッドでクラスを使用して計算を実行します。

private static long calculateSum(int n) {
    return new ConsecutiveNumbersSum(n).getTotalSum();
}

最後に、メソッドをさまざまな回数呼び出して、何が起こるかを確認します。

for (int i = 1; i < NUMBERS_OF_ITERATIONS; i++) {
    calculateSum(i);
}

最初の実行では、1,000回実行します(上記のしきい値10,000未満)。 出力でcalculateSum()メソッドを検索しても、見つかりません。 何度も呼ばなかったので、これは予想されることです。

ここで、反復回数を15,000に変更し、出力を再度検索すると、次のように表示されます。

664 262 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
  @ 10   com.baeldung.inlining.InliningExample::calculateSum (12 bytes)   inline (hot)

今回は、メソッドがインライン化の条件を満たし、JVMがインライン化したことがわかります。

メソッドが大きすぎると、反復回数に関係なく、JITはメソッドをインライン化しないことに再度言及することは注目に値します。 これは、アプリケーションの実行時に別のフラグを追加することで確認できます。

-XX:FreqInlineSize=10

前の出力でわかるように、メソッドのサイズは12バイトです。 -XX: FreqInlineSize フラグは、インライン化に適格なメソッドサイズを10バイトに制限します。 したがって、今回はインライン化を行わないでください。 実際、出力をもう一度見てみると、これを確認できます。

330 266 % com.baeldung.inlining.InliningExample::main @ 2 (21 bytes)
  @ 10   com.baeldung.inlining.InliningExample::calculateSum (12 bytes)   hot method too big

ここでは説明のためにフラグ値を変更しましたが、どうしても必要な場合を除いて、 -XX:FreqInlineSizeフラグのデフォルト値を変更しないことを強調する必要があります。

5. 結論

この記事では、JVMでのインライン化メソッドと、JITがそれを実行する方法について説明しました。 メソッドがインライン化に適格かどうかを確認する方法を説明し、インライン化するには大きすぎる頻繁に呼び出される長いメソッドのサイズを縮小することで、この情報を利用する方法を提案しました。

最後に、実際にホットな方法を特定する方法を説明しました。

この記事に記載されているすべてのコードスニペットは、GitHubリポジトリにあります。