8 ガベージファースト・ガベージ・コレクタのチューニング

この項では、ユーザーの要件を満たさない場合にガベージファースト・ガベージ・コレクタ(G1 GC)の動作を適応する方法を説明します。

G1の一般的な推奨事項

一般的な推奨事項は、G1をデフォルト設定で使用することです。異なる一時停止時間目標を設定したり、必要に応じて-Xmxを使用して、最大Javaヒープ・サイズを設定することもあります。

G1のデフォルト設定は、他のコレクタと異なってバランスが取られています。デフォルト構成のG1の目標は、最大スループットでもなく、最低のレイテンシでもなく、高いスループットで相対的に小さな統一された一時停止を行うことです。ただし、ヒープおよび一時停止時間の制御において領域を増分的に回収するG1のメカニズムにより、アプリケーション・スレッドにおいても、領域回収の効率性においてもオーバーヘッドが生じます。

高いスループットを優先する場合、-XX:MaxGCPauseMillisを使用して一時停止時間目標を緩くするか、より大きいヒープを提供します。レイテンシが主要な要件である場合、一時停止時間ターゲットを変更します。若い世代のサイズはG1が一時停止時間を満たすことを可能にする主要な手段であるため、-Xmn-XX:NewRatioなどのオプションを使用することによって若い世代のサイズを特定の値に制限することを回避します。若い世代のサイズを単一の値に設定すると、一時停止時間の制御をオーバーライドし、実際に無効化します。

他のコレクタからG1への移行

一般的に、他のコレクタ、特にコンカレント・マーク・スイープ・コレクタからG1に移行する際に、ガベージ・コレクションに影響するオプションをすべて削除して開始し、-Xmxとオプションで-Xmsを使用して、一時停止時間目標と全体のヒープ・サイズのみを設定します。

特定の方法で応答する他のコレクタに便利な多数のオプションは、まったく影響を及ぼさないか、スループットを低減するか、一時停止時間ターゲットを満たす可能性を小さくします。たとえば、若い世代のサイズを設定して、一時停止時間目標を満たすようにG1が若い世代のサイズを調整しないようにできます。

G1パフォーマンスの向上

G1は、追加のオプションを指定することなく、全体的に適切なパフォーマンスを提供するように設計されています。ただし、デフォルトのヒューリスティックまたはデフォルト構成が最適以下の結果になる場合があります。この項では、これらの場合における診断および改善に関するガイドラインを示します。このガイドでは、特定のアプリケーションの場合の、選択したメトリックにおいてガベージ・コレクタのパフォーマンスを向上させるためにG1が提供する可能性についてのみ説明します。場合によっては、アプリケーションレベルの最適化のほうが、(寿命が長くないオブジェクトでの問題のある状況の回避など)パフォーマンスを改善するようにVMをチューニングするよりも効果的なことがあります。

診断目的で、G1では包括的なロギングを提供しています。-Xlog:gc*=debugオプションを使用して、必要に応じて出力を調整することをお薦めします。ログでは、ガベージ・コレクション・アクティビティの一時停止中および一時停止外の詳細情報が提示されます。これには、コレクションのタイプおよび一時停止の特定のフェーズに費やされた時間の内訳が含まれています。

次の項で、共通のパフォーマンスの問題について説明します。

フル・ガベージ・コレクションの監視

フル・ヒープ・ガベージ・コレクション(フルGC)は、時間がかかることがあります。ログ内でPause Full (G1 Compaction Pause)という語を検索すると、古い世代の高ヒープ占有率によって発生したフルGCを検出できます。通常、フルGCの前に、(Evacuation Failure)タグで示される退避の失敗が発生したガベージ・コレクションが発生します。

フルGCが発生する理由は、回収できなくなるほど多数のオブジェクトをアプリケーションが割り当てるためです。コンカレント・マーキングは領域回収フェーズの開始に間に合うように完了できないことがあります。フルGCに移行する確率は、多数の大型オブジェクトの割当てにより大きくなることがあります。G1におけるこれらのオブジェクトの割当て方法よっては、想定よりも時間がかかることがあります。

目標は、コンカレント・マーキングが時間どおりに終了することを確認するためのものです。これを実現するには、古い世代の割当て率を減らすか、コンカレント・マーキングの完了に時間をかけるようにします。

G1には、このような状況により適切に対処するためのオプションがいくつかあります。

  • Javaヒープ上で大型オブジェクトが占有しているリージョンの数を調べるには、gc+heap=infoロギングを使用します。 「Humongous regions: X->Y」行のYは、大型オブジェクトが占有しているリージョンの量を示しています。この数が古いリージョンの数よりも大きい場合、最適なオプションはこのオブジェクトの数を減らしてみることです。これを実現するには、  -XX:G1HeapRegionSizeオプションを使用してリージョン・サイズを増やします。現在選択されたヒープ・リージョン・サイズは、ログの開始時に出力されます。
  • Javaヒープのサイズを増やします。これは、通常、マーキングが完了する必要がある時間を増やします。
  • -XX:ConcGCThreadsを明示的に設定して、コンカレント・マーキング・スレッド数を増やします。
  • G1でマーキングを早く開始するように強制します。G1では、より早いアプリケーションの動作に基づいた開始ヒープ占有率(IHOP)しきい値を自動的に判定します。アプリケーションの動作が変化すると、これらの予測が当てはまらない場合があります。2つのオプションがあります: -XX:G1ReservePercentを変更して適応型IHOPの計算で使用されるバッファを増やすことにより、領域回収の開始タイミング用にターゲット占有率を低くするか、-XX:-G1UseAdaptiveIHOP-XX:InitiatingHeapOccupancyPercentを使用して手動設定により、IHOPの適応型計算を無効にします。

割当て失敗以外の原因によるフルGCの発生は、通常、アプリケーションまたは一部の外部ツールがフル・ヒープ・コレクションの原因であることを示します。原因がSystem.gc()であり、アプリケーション・ソースを変更できない場合、フルGCの影響は-XX:+ExplicitGCInvokesConcurrentを使用すると、低減できます。あるいは、-XX:+DisableExplicitGCを設定すると、VMにこれらを完全に無視させることもできます。外部ツールはフルGCを強制することがあります。これらを削除するには、フルGCをリクエストしないようにします。

大型オブジェクトの断片化

連続的なリージョン・セットを見つける必要があるため、すべてのJavaヒープ・メモリーが枯渇する前に、フルGCが発生することがあります。この場合に考えられるオプションは、オプション-XX:G1HeapRegionSizeを使用してヒープ・リージョン・サイズを増やして大型オブジェクト数を減らすか、ヒープ・サイズを増やすことです。極端な場合、使用可能なメモリーが十分にあっても、G1がオブジェクトを割り当てるのに使用可能な連続的な領域が十分にない可能性があります。これにより、フルGCで連続的な領域を十分に回収できないと、VMが終了します。したがって、前述のように、大型オブジェクトの割当て量を減らすか、ヒープを増やす以外の選択肢はありません。

レイテンシのためのチューニング

この項では、一時停止時間が長すぎるなど、共通のレイテンシの問題がある場合に、G1動作を改善するヒントについて説明します。

通常と異なるシステムまたはリアル・タイムの使用状況

ガベージ・コレクションの一時停止ごとに、gc+cpu=infoのログ出力には、一時停止時間中に費やされた時間の内訳とともにオペレーティング・システムの情報を記載する行が含まれています。このような出力の例は、User=0.19s Sys=0.00s Real=0.01sです。

ユーザー時間はVMコードで費やされた時間で、システム時間はオペレーティング・システムで費やされた時間で、リアル・タイムは一時停止時に経過した絶対時間です。システム時間が比較的高い場合、たいていは環境に原因があます。

システム時間が長い場合の共通の既知の問題は、次のとおりです。

  • VMがオペレーティング・システムのメモリーからメモリーを割り当てたり、戻したりすると、不要な遅延が発生することがあります。遅延を回避するには、オプション-Xms -Xmxを使用して最小および最大ヒープ・サイズを同じ値に設定し、-XX:+AlwaysPreTouchを使用してすべてのメモリーをプレタッチしてこの作業をVM起動フェーズに移動します。
  • 特にLinuxでは、透過的huge pages (THP)機能によって小さなページを大きなページに結合すると、一時停止時だけでなく、ランダムにプロセスの速度が低下することがあります。VMが大量のメモリーを割り当てたり保持したりするため、長時間VMの処理速度が低下するリスクが通常よりも高くなります。透過的huge pages機能を無効にする方法は、オペレーティング・システムのドキュメントを参照してください。
  • ログ出力に書き込むと、ログが書き込まれるハード・ディスクのすべてのI/O帯域幅を断続的に占有するバックグラウンド・タスクのためにしばらくの間速度が低下することがあります。ログまたは他のストレージ(メモリー指定されたファイル・システムなど)に別のディスクを使用して、これを回避することを検討してください。

リアル・タイムが他の合計時間よりも長くなる場合がありますが、これは、過負荷になった可能性のあるマシンで十分なCPU時間をVMが得られなかったことを示すことがあります。

処理に長時間かかる参照オブジェクト

参照オブジェクトの処理にかかる時間に関する情報は、Reference Processingフェーズに示されています。Reference Processingフェーズ時に、G1では特定タイプの参照オブジェクトの要件に従って、参照オブジェクトの参照を更新します。デフォルトでは、-XX:ReferencesPerThread個の参照オブジェクトごとに1つのスレッドを開始し、スレッド数は-XX:ParallelGCThreadsの値で制限されるというヒューリスティックを使用して、G1がReference Processingのサブフェーズを並列化します。このヒューリスティックを無効にするには、-XX:ReferencesPerThreadをゼロ(0)に設定して、デフォルトで使用可能なすべてのスレッドを使用するか、-XX:-ParallelRefProcEnabledで並列化を完全に無効化します。

時間がかかりすぎる若い世代のみのフェーズでの若い世代のみのコレクション

通常の若い世代のコレクション、すなわち一般的に若いコレクションは、若い世代のサイズ(具体的に言うと、コピーする必要のあるコレクション・セット内のライブ・オブジェクト数)に比例して時間がかかります。退避コレクション・セットフェーズが特にオブジェクト・コピー・サブフェーズで時間がかかりすぎる場合、-XX:G1NewSizePercentを減らします。これによって若い世代の最小サイズが小さくなり、一時停止が短くなる可能性があります。

アプリケーション・パフォーマンスや、特にコレクションを経て存続するオブジェクトの量が突然変化した場合、若い世代のサイズに関する別の問題が発生することがあります。これによって、ガベージ・コレクションの一時停止時間が大幅に増加する場合があります。-XX:G1MaxNewSizePercentを使用すると、若い世代の最大サイズの削減に役立つ場合があります。これは若い世代の最大サイズおよび一時停止時に処理する必要のあるオブジェクト数を制限します。

時間がかかりすぎる混合コレクション

混合の若いコレクションは、古い世代の領域を回収する際に使用します。混合コレクションのコレクション・セットには若い世代と古い世代のリージョンが含まれています。gc+ergo+cset=debugログ出力を有効にすると、若い世代または古い世代のいずれかのリージョンの退避が一時停止時間に影響した時間に関する情報を取得できます。次のログ・メッセージを探します:

Added young regions to CSet. [...] predicted eden time: 4.86ms, predicted base time: 9.98ms, target pause time: 200.00ms, [...]

Eden時間とベース時間によって予測される若いリージョン・タイムが提供されます。これは、若い世代の退避に要するとG1が予想する時間です

古いリージョン時間を予測するためのログ・メッセージは次のようになります:

Finish choosing collection set old regions. [...] predicted initial time: 147.70ms, predicted optional time: 15.45ms, [...]

この場合、予測された初期時間は、予測された古いリージョン時間を表します。つまり、古い世代のリージョンの最小セットの退避に要するとG1が予想する時間です。

予測した若い世代のリージョン時間が長すぎる場合、オプションは、「時間がかかりすぎる若い世代のみのフェーズでの若い世代のみのコレクション」を参照してください。これ以外の場合、古い世代のリージョンの一時停止時間への影響を少なくするために、G1では次の3つのオプションを提供しています。

  • -XX:G1MixedGCCountTargetを増やして、さらに多くのガベージ・コレクションで古い世代のリージョンの回収を拡大します。

  • -XX:G1MixedGCLiveThresholdPercentを使用して、リージョンを候補コレクション・セットに移動しないで、収集に比例的に時間がかかるリージョンを収集しないようにします。多くの場合、多く占有しているリージョンは収集に時間がかかります。

  • G1が多く占有しているリージョンを多数収集しないように、古い世代の領域回収を早めに停止します。この場合、 -XX:G1HeapWastePercentを増やします。

最後の2つのオプションは、現在の領域回収フェーズで領域を回収できる、コレクション・セットの候補リージョンの量を減らすことに注意してください。これは、持続した操作に対して古い世代の十分な領域をG1で回収できないことがあるという意味です。ただし、後の領域回収フェーズで、これらに対してガベージ・コレクションを実行できます。

コレクションが連続して発生

G1のデフォルトMMU設定では、連続的なガベージ・コレクションが可能です。-XX:GCPauseIntervalMillisのデフォルト値は、-XX:MaxGCPauseMillisよりわずかに高くなります。進行しないアプリケーションが原因で発生する連続的なガベージ・コレクションが見られる場合は、-XX:GCPauseIntervalMillisの値を許容可能な値に増やします。G1は、ガベージ・コレクションの間隔を空けようとします。

ヒープ・ルートのハイ・マージおよびヒープ・ルートのスキャン時間

これらのフェーズを減らす1つの方法は、組み合された記憶済セット内の記憶済セット・エントリの数を減らすことです。オプション-XX:G1HeapRegionSizeを使用してヒープ・リージョンのサイズを調整すると、リージョン間参照数と記憶済セットのサイズが減ります。リージョンが大きくなると、リージョン間参照の数は少なくなるので、これらの処理に費やされる相対的な作業量が減少します。ただし、同時に、大きなリージョンはリージョンごとに退避するライブ・オブジェクトが増え、他のフェーズの時間が増加します。

ガベージ・コレクションの時間の多く(60%以上)がこの2つのフェーズで消費されている場合、1つのオプションとして、-XX:GCCardSizeInBytesオプションの値を減らすことで記憶済セット・エントリの粒度を低下させることができます。粒度がより細かくなると、若干の追加メモリーと引き換えに参照を検索する作業量が減少します。

大きなオブジェクトを割り当てるアプリケーションと組み合せた、不要に高いヒープ・ルートのスキャン時間は、一括実行によって記憶済セットの同時更新作業を減らそうとする最適化が原因で発生することがあります。このようなバッチを作成したアプリケーションがガベージ・コレクションの直前に発生する場合、これはヒープ・ルートのマージ時間に悪影響を及ぼす可能性があります。-XX:-ReduceInitialCardMarksを使用して、この最適化を無効にすると、この状況を回避できる可能性があります。

スループットのためのチューニング

G1のデフォルト・ポリシーはスループットとレイテンシ間でバランスを取ろうとしますが、より高いスループットが望ましい場合があります。前述の項で説明した全体的な一時停止時間の短縮とは別に、一時停止の頻度を減らすことができます。これには、-XX:MaxGCPauseMillisを使用して最大一時停止時間を増やします。世代のサイズ設定のヒューリスティックでは自動的に若い世代のサイズを適応させ、これにより、一時停止の頻度を直接判定します。これによって期待したように動作しない場合、特に領域回収フェーズで、-XX:G1NewSizePercentを使用して若い世代の最小サイズを増やして、G1でこれを実行するように強制します。

場合によっては、若い世代の最小許容サイズの-XX:G1MaxNewSizePercentで若い世代のサイズを制限することにより、スループットを制限できます。gc+heap=infoロギングのリージョン・サマリー出力で確認することにより、これを診断できます。ここで、EdenリージョンとSurvivorリージョンを組み合せた割合は、リージョンの合計値の-XX:G1MaxNewSizePercentパーセントに近くなります。この場合、-XX:G1MaxNewSizePercentを増やすことを検討してください。

スループットを向上させるもう1つのオプションは、同時作業の量を減らすことです。特に、記憶済セットの同時更新では、多くのCPUリソースが必要になることがよくあります。オプション-XX:G1RSetUpdatingPauseTimePercentを使用すると、同時操作の処理をガベージ・コレクションの一時停止に移すことができます。

この値を増やすと、アプリケーションと同時にスケジュールされている調整作業が減る可能性があります。逆に、この値を減らすと、アプリケーションと同時に実行される調整作業の量が増える可能性があります。

ガベージ・コレクションの一時停止中の調整作業は、gc+phases=debugロギングを有効にしたとき、Merge Heap RootsフェーズのLog Buffers部分で追跡されます。

-XX:+UseLargePagesを使用して大きなページの使用を有効にして、スループットを向上させることもできます。大きなページの設定方法は、ご使用のオペレーティング・システムのマニュアルを参照してください。

これを無効にすると、ヒープのサイズ変更作業を最小化でき、オプション-Xms-Xmxを同じ値に設定します。さらに、-XX:+AlwaysPreTouchを使用して、オペレーティング・システムによる仮想メモリーと物理メモリーの作業をVMの起動時に移動します。これらの両方の措置は、一時停止をより一貫したものにするために特に望ましい場合があります。

ヒープ・サイズのためのチューニング

他のコレクタと同様に、G1では、ガベージ・コレクションで費やされる時間が-XX:GCTimeRatioオプションで決定される比率を下回るように、ヒープのサイズを設定しようとします。このオプションを調整して、G1がユーザーの要件を満たすようにします。

チューニング可能なデフォルト設定

この項では、このトピックで紹介されているコマンド行オプションのデフォルト値と追加情報について説明します。

表8-1 G1 GCのチューニング可能なデフォルト設定

オプションとデフォルト値 説明

-XX:+ReduceInitialCardMarks

これは、初期オブジェクトの割当てのために記憶済セットの同時実行(調整)作業を一括実行します。

-XX:+ParallelRefProcEnabled

-XX:ReferencesPerThread=1000

-XX:ReferencesPerThreadによって並列度が決まります。N個の参照オブジェクトごとに1つのスレッドがReference Processingのサブフェーズに追加され、スレッド数は-XX:ParallelGCThreadsで制限されます。値がゼロ(0)の場合、-XX:ParallelGCThreads値で指定されている最大数のスレッドが常に使用されます。 

これは、java.lang.Ref.*インスタンスの処理を複数のスレッドで並行で実行するかどうかを判定します。

-XX:G1RSetUpdatingPauseTimePercent=10 

記憶済セットの同時更新(調整)作業は、このオプションを使用して制御できます。調整は、作業を同時にスケジュールしようとします。このため、最長一時停止目標の最大-XX:G1RSetUpdatingPauseTimePercentパーセントが、RSの更新フェーズにおけるガベージ・コレクションの一時停止で費やされて、残りの作業を処理します。

-XX:G1SummarizeRSetStatsPeriod=0 

これは、G1が記憶済セットのサマリー・レポートを生成する、多数のGCにおける期間です。無効にするには、これをゼロに設定します。記憶済セットのサマリー・レポートの生成はコストのかかる操作であるため、必要な場合にのみ、かなり高い値を設定してこれを使用してください。出力するには、gc+remset=traceを使用します。

-XX:GCTimeRatio=12 

これは、アプリケーションとは対照的に、ガベージ・コレクションで費やす必要がある時間の対象となる比率の除数です。ヒープを増やす前にガベージ・コレクションで費やすことができる時間の小数を判定するための実際の式は、1 / (1 + GCTimeRatio)です。このデフォルト値は、ガベージ・コレクションで費やされる時間の約8%の目標値になります。

-XX:G1PeriodicGCInterval=0

G1が定期的なガベージ・コレクションをトリガーする必要があるかどうかを確認する間隔(ミリ秒)。無効にするにはゼロに設定します。

-XX:+G1PeriodicGCInvokesConcurrent

設定されている場合は、定期的なガベージ・コレクションでコンカレント・マーキングがトリガーされるか、既存のコレクション・サイクルが継続され、設定されていない場合はフルGCがトリガーされます。

-XX:G1PeriodicGCSystemLoadThreshold=0.0 ホストのgetloadavg()コールで返される現在のシステム負荷のしきい値で、定期的なガベージ・コレクションをトリガーする必要があるかどうかを判定します。現在のシステム負荷がこの値よりも高いと、定期的なガベージ・コレクションは実行されません。値ゼロは、このしきい値チェックが無効であることを示します。

ノート:

<ergo>は、実際の値が環境によってエルゴノミックに決定されるという意味です。