15 チューニング
この章では、ジャスト・イン・タイムの(JIT)コンパイラのコンパイル済メソッドを格納するコードキャッシュ内のメモリー消費量を削減する手法について説明します。
この章は、次の項目で構成されています:
入門
Java Virtual Machine (JVM)は、ネイティブ・コードを生成し、コードキャッシュと呼ばれるメモリー領域に格納します。 JVMは、動的に生成されたインタプリタ・ループ、Java Native Interface (JNI)スタブ、およびジャスト・イン・タイム(JIT)コンパイラによってネイティブ・コードにコンパイルされるJavaメソッドなど、様々な理由でネイティブ・コードを生成します。 JITは、コードキャッシュの最大のユーザーです。 この付録では、良好なパフォーマンスを維持しながら、JITコンパイラのコードキャッシュの使用量を削減する方法を説明します。
この章では、JITのコードキャッシュの使用を減らす3つの方法について説明します:
-
JITで使用できるコードキャッシュの量を制限します。
-
JITをチューニングして、より少ないメソッドをコンパイルします。
-
メソッドごとに少ないコードを生成するようにJITをチューニングします。
java Launcher Codecacheオプションのサマリー
この項の表に示されているjava
ランチャによって渡される「JVM」オプションを使用して、JITで使用されるコードキャッシュの量を減らすことができます。 表の説明はサマリーです。 ほとんどのオプションについては、以降のセクションで詳しく説明します。
javaコマンドのCodecacheオプションの使用方法
次のセクションに示すオプションは、次の特徴を共有します。
-
すべてのオプションは、
-XX
オプション(-XX:InitialCodeCacheSize=32m
など)です。 true/falseの値を持つオプションは、trueの場合は+
、falseの場合は-
を使用して指定します。 たとえば、-XX:+PrintCodeCache
はこのオプションをtrueに設定します。 -
"変化"がデフォルト値としてリストされているオプションについては、
XX:+PrintFlagsFinal
を指定してランチャを実行して、プラットフォームのデフォルト値を確認します。 -
オプションのデフォルト値が(クライアントまたはサーバー)を使用しているJVMによって異なる場合、両方のデフォルトが/'で区切られて表示されます。 クライアントJVMのデフォルトが最初にリストされます。 最小JVMは、クライアントJVMと同じJITを使用するため、デフォルトは同じです。
Codecacheフラッシュ・オプション
「表15-2」は、コードキャッシュ・フラッシュ・オプションの概要を示します。
表15-2 Codecacheフラッシュ・オプション
オプション | デフォルト | 説明 |
---|---|---|
|
false |
キャッシュがいっぱいになったらJVMを終了 |
|
false |
コンパイラをシャットダウンする前にコードキャッシュをスイープしてみてください |
|
30 |
コードキャッシュ・スイープ・セッション間の最小秒数 |
|
500K |
指定した容量より少ない領域が残っている場合は、コンパイルを停止します。 この領域は、ネイティブ・アダプタなど、コンパイルされたメソッドではないコード用に予約されています。 |
Codecacheの使用量の測定
コードキャッシュの使用量削減作業の成功を測定するには、コードキャッシュの使用量とパフォーマンスへの影響を測定する必要があります。 この項では、コードキャッシュの使用方法を測定する方法について説明します。 アプリケーションのパフォーマンスを測定する最善の方法を決定するのは、ユーザー次第です。
ベースライン(コード・キャッシュの削減技術が適用されていないときに使用されるコードキャッシュの量)から開始し、コードカッシュの削減手法がベースラインに対するコードカッシュのサイズとパフォーマンスの両方に与える影響を監視します。
コードキャッシュは比較的小さく始まり、新しいメソッドがコンパイルされると必要に応じて大きくなることに注意してください。 コンパイルされたメソッドは、特にコードキャッシュの最大サイズが制約されている場合、コードキャッシュから解放されることがあります。 freeメソッドによって使用されるメモリーは、新しくコンパイルされたメソッドに再利用できるため、コードカッシュをさらに増やすことなく、追加のメソッドをコンパイルできます。
コードキャッシュの使用状況に関する情報は、java
ランチャ・コマンドラインで-XX:+PrintCodeCache
を指定することで取得できます。 アプリケーションが終了すると、次のような出力が表示されます:
CodeCache: size=32768Kb used=542Kb max_used=542Kb free=32226Kb bounds [0xb414a000, 0xb41d2000, 0xb614a000] total_blobs=131 nmethods=5 adapters=63 compilation: enabled
コード・キャッシュの削減作業のための出力の最も有用な部分は、最初の行です。 次に、出力される各値について説明します:
-
size
: コードキャッシュの最大サイズ。 これは、-XX:ReservedCodeCacheSize
で指定されたものと同等である必要があります。 これは、コードカッシュによって使用される物理メモリー(RAM)の実際の量ではないことに注意してください。 これは、そのために確保されている仮想アドレス空間の量にすぎません。 -
used
: 実際に使用されているメモリーの量。 これは通常、コードキャッシュが占めるRAMの量です。 ただし、断片化と、コードカッシュ内の空きブロックと割り当てられたメモリー・ブロックの混在により、コードカッシュがこの値で示されるよりも多くのRAMを占める可能性があります。これは、その時点で解放されたブロックがまだRAM内にある可能性があるためです。 -
max_used
: これはコードキャッシュの使用のための高い水位標であり、コードキャッシュが成長して使用した最大サイズです。 これは通常、コードキャッシュによって占有されているRAMの量とみなされ、ある時点で使用されていたコードキャッシュに空きメモリーが含まれます。 このため、アプリケーションが使用しているコードキャッシュの量を決定する際に使用する番号です。 -
free
: これは、size
からused
を引いた値です。
-XX:+PrintCodeCacheOnCompilation
オプションは、-XX:+PrintCodeCache
で生成された最初の行と同じ出力も生成しますが、メソッドがコンパイルされるたびに同じ出力を生成します。 これは、終了しないアプリケーションの測定に役立ちます。 また、アプリケーションの起動完了後など、アプリケーションの実行の特定の時点でのコードキャッシュの使用に関心がある場合に役立ちます。
max_used
は通常、コードキャッシュによって使用されるRAMの量を表すため、これはベースライン測定を行うときに注意する必要がある値です。 次の項では、max_used
を減らす方法について説明します。
コードキャッシュのサイズの制約
コードキャッシュのサイズを制限すると、コードキャッシュは、制約のないコードキャッシュが使用するものよりも小さいサイズに制限されます。 ReservedCodeCacheSize
オプションは、コードキャッシュの最大サイズを決定します。 デフォルトでは、クライアントJVMは32MB以上、サーバーVMは48MB以上になります。 ほとんどのJavaアプリケーションでは、このサイズが非常に大きいため、アプリケーションはコードキャッシュ全体を埋めることはありません。 したがって、コーデカチは制約なしとみなされ、JITはコンパイルする必要があると考えられるコードをすべてコンパイルし続けます。
コードキャッシュのサイズを制約すると便利なのはいつですか?
新しいメソッド・セットが"暑い"になるような状態変更を行うアプリケーションは、制約されたコードキャッシュから大きなメリットを得ることができます。
一般的な状態の変更は、起動から通常の実行までです。 アプリケーションは起動時に多くのコンパイルをトリガーする可能性がありますが、起動後にこのコンパイル済コードはほとんど必要ありません。 コードキャッシュを制限することで、起動時にコンパイルされたコードをスローするようにコードキャッシュ・フラッシュをトリガーし、アプリケーションの実行中に必要なコードを使用できるようにします。
一部のアプリケーションでは、実行中に状態が変更され、長時間新しい状態になる傾向があります。 これらのアプリケーションでは、コードキャッシュは、特定の状態に必要なコンパイル済コードを保持するのに十分な大きさである必要があります。 したがって、アプリケーションに5つの異なる状態があり、それぞれが適切に実行するために約1MBのコードキャッシュが必要な場合、コードキャッシュを1MBに制限できます。これは、アプリケーションの通常の5MBのコードキャッシュの使用量よりも80%削減されます。 ただし、アプリケーションが状態を変更するたびに、JITが新しい状態に必要なメソッドをコンパイルしている間に、パフォーマンスが低下することに注意してください。
コードキャッシュのサイズを制限する方法
コードキャッシュが(使用方法がReservedCodeCacheSize
に近づくか、)を制約してより多くのメソッドをコンパイルする場合、JITは最初にコンパイル済のいくつかのメソッドをスローする必要があります。 コンパイルされたメソッドの破棄は、コードキャッシュ・フラッシュと呼ばれます。 UseCodeCacheFlushing
オプションは、キャッシュのフラッシュのオンとオフを切り替えます。 デフォルトではオンです。 この機能は、XX:-UseCodeCacheFlushing
を指定して無効にできます。 有効にすると、コードキャッシュで使用可能なメモリーが少ないときにフラッシュがトリガーされます。 コードキャッシュを制限する場合は、コードキャッシュのフラッシュを有効にすることが重要です。 フラッシュが無効になっている場合、JITはコードキャッシュがいっぱいになったあとにメソッドをコンパイルしません。
アプリケーションに適したReservedCodeCacheSize
値を決定するには、まず、コードキャッシュが制約されていないときにアプリケーションが使用するコードキャッシュの量を確認する必要があります。 「Codecacheの使用量の測定」で説明されているXX:+PrintCodeCache
オプションを使用し、max_used
値(アプリケーションが使用するコードキャッシュの量)を調べます。 その後、ReservedCodeCacheSize
を小さい値に設定して、アプリケーションのパフォーマンスを確認できます。
小さい(5MB未満)コードキャッシュを使用する場合は、CodeCacheMinimumFreeSpace
を考慮する必要があります。 大きなコーデカルの場合、デフォルト値はそのままにします。 一般に、JITは、このオプションを尊重するのに十分なスペースをコードキャッシュに保持します。 小さいコード・キャッシュの場合は、新しいReservedCodeCacheSize
にCodeCacheMinimumFreeSpaceを追加します。 たとえば、次のようにします:
max_used = 3M CodeCacheMinimumFreeSpace = 500k
コードキャッシュ・サイズを3MBから2MBに減らすには、ReservedCodeCacheSize
を2500k (2M+500K)に増やします。変更後、max_used
が2Mに変更されていることを確認します。
コードキャッシュを制約する場合は、通常、CodeCacheMinimumFreeSpace
を小さい値に設定できます。 ただし、CodeCacheMinimumFreeSpace
は100KB以上である必要があります。 空き領域が不足すると、JVMはVirtualMachineError
をスローして終了するか、まれにクラッシュします。 3Mバイトから2Mバイトの例では、次の設定が適しています:
-XX:ReservedCodeCacheSize=2100k -XX:CodeCacheMinimumFreeSpace=100k
ニーズに最適なReservedCodeCacheSize
を見つけることは、反復的なプロセスです。 アプリケーションのパフォーマンスが許容範囲外に低下し、許容可能なパフォーマンスが再度得られるまで、ReservedCodeCacheSize
に小さい値および小さい値を繰り返し使用できます。 また、達成している増分リターンも測定する必要があります。 わずか5%のパフォーマンス低下でmax_used
を50%削減でき、10%のパフォーマンス低下でmax_used
を60%削減できる場合があります。 この例では、2番目の10%のコードキャッシュの削減コストは、最初の50%のコードキャッシュの削減と同じくらいです。 この場合、コードキャッシュの使用量とパフォーマンスとのバランスが50%低下すると結論付けることができます。
コンパイルの削減
コンパイルされたメソッドの数、またはコンパイルされる速度を減らすことは、使用されるコード・キャッシュの量を減らすもう1つの有効な方法です。 積極的なメソッドのコンパイル方法に影響する2つの主要なコマンドライン・オプション: CompileThreshold
およびOnStackReplacePercentage
。 CompileThreshold
は、メソッドのコンパイル前に必要なメソッド呼出しの数に関連しています。 OnStackReplacePercentage
は、メソッドがコンパイルされる前に取得された後方分岐の数に関連し、CompileThreshold
の割合で指定されます。 メソッドの逆方向ブランチと呼出しの結合数がCompileThreshold
* OnStackReplacePercentage
/ 100に達するか、それを超えると、メソッドがコンパイルされます。 BackEdgeThreshold
というオプションもありますが、現在は何も行いません。 かわりにOnStackReplacePercentage
を使用してください。
これらのオプションの値を大きくすると、コンパイルが減少します。 オプションをデフォルトよりも大きく設定すると、メソッドがコンパイルされるときに(または再コンパイル)が遅延し、メソッドがコンパイルされない可能性があります。 通常、これらのオプションを大きい値に設定すると、パフォーマンス(メソッドの解釈)も低下するため、パフォーマンスとコードキャッシュの両方の使用状況を調整するときに監視することが重要です。 クライアントJVMの場合、これらのオプションのデフォルト値のトリプルは適切な開始点です。 サーバーJVMでは、CompileThreshold
はすでにかなり高い値に設定されているため、これ以上調整する必要はありません。
コンパイル済メソッド・サイズの削減
コンパイルされたメソッドのサイズを小さくするコマンドライン・オプションが多数ありますが、通常はパフォーマンス・コストがかかります。 他のセクションで説明したコードキャッシュの削減メソッドと同様に、キーは、パフォーマンスが大幅に低下することなく、コード・キャッシュの使用率を低下させる設定を見つけることです。
このセクションで説明するオプションはすべて、コンパイラが実行するインライン化の量を減らすことに関連しています。 インライン化とは、コンパイラが呼び出されたメソッドのコードを、コンパイルされるメソッドのコンパイルされたコードに組み込むことです。 インライン化は多くのレベルの深さで行うことができるため、メソッドa()
がc()
をコールするb()
をコールすると、a()
をコンパイルするときにb()
をa()
にインライン化できます。これにより、c()
のインライン化をa()
にトリガーできます。
JITコンパイラは、複数のヒューリスティックを使用して、メソッドをインライン化する必要があるかどうかを判断します。 一般に、ヒューリスティックは最適なパフォーマンスのためにチューニングされます。 ただし、それらのいくつかを調整して、より少ないコードキャッシュ使用のためにパフォーマンスを犠牲にすることができます。 チューニングするより便利なオプションを次に説明します:
- InlineSmallCode
-
このオプションの値によって、コンパイル中のメソッドから呼び出されたときに、すでにコンパイルされているメソッドがインライン化される必要があるサイズが決まります。 このメソッドのコンパイル済バージョンが
InlineSmallCode
の設定より大きい場合、インライン化されません。 かわりに、メソッドのコンパイル済バージョンへのコールが生成されます。 - MaxInlineLevel
-
このオプションは、インライン化コール・チェーンの最大ネスト・レベルを表します。 インライン化は、より深いレベルではあまり役に立たなくなり、最終的にコードが膨らむためにパフォーマンスに悪影響を及ぼす可能性があります。 デフォルト値は9です。
MaxInlineLevel
を1または2に設定すると、getterやsetterなどの簡単なメソッドがインライン化されます。 - MaxInlineSize
-
このオプションは、インライン化されたメソッドの最大バイトコード・サイズを表します。 デフォルトは35ですが、各インライン・レベルで自動的に削減されます。 非常に小さい(約6)を設定すると、簡単なメソッドのみがインライン化されます。
- MinInliningThreshold
-
インタプリタは、メソッド・コール・サイトで呼出し回数を追跡します。 このカウントは、呼び出されたメソッドをインライン化する必要があるかどうかを判断するためにJITによって使用されます。 メソッドの呼出し回数が
MinInliningThreshold
より少ない場合、メソッドはインライン化されません。 このしきい値を大きくすると、インライン化されるメソッド・コール・サイトの数が減少します。 簡易メソッドは、常にインライン化され、MinInliningThreshold
の設定の対象にはなりません。 - InlineSynchronizedMethods
-
このオプションを使用すると、
synchronized
として宣言されているメソッドのインライン化を無効にできます。 同期は、特にマルチ・コア・デバイスではかなり高価な操作であるため、小さい同期方式でもインライン化の利点は大幅に減少します。 同期メソッドのインライン化は、パフォーマンス低下がほとんどまたはまったく認識されず、コードキャッシュの使用量が著しく減少している場合に、無効にできます。