4 フライト・レコーダを使用したパフォーマンス問題のトラブルシューティング

Javaアプリケーションのパフォーマンスの問題を識別し、フライト・レコーダからの記録を使用してそれらの問題をデバッグします。

フライト・レコーダでの記録の作成の詳細は、「フライト記録の生成」を参照してください。

フライト・レコーダで提供されるデータは、パフォーマンス上の問題を調査する際に役立ちます。パフォーマンス・オーバーヘッドによって結果をゆがめることなく、これほど多くのプロファイリング・データを提供するツールは他にありません。この章では、フライト・レコーダからのデータを使用して識別およびデバッグできるパフォーマンスの問題について説明します。

この章の構成は、次のとおりです。

フライト・レコーダのオーバーヘッド

パフォーマンスを測定する場合は、フライト・レコーダによって追加されるパフォーマンス・オーバーヘッドを考慮することが重要です。オーバーヘッドは、アプリケーションに応じて異なります。パフォーマンス・テストがセット・アップされている場合は、特定のアプリケーションに顕著なオーバーヘッドが存在するかどうかを測定できます。

デフォルトの設定を使用して標準のプロファイリング記録を記録するためのオーバーヘッドは、ほとんどのアプリケーションで2%未満です。通常、標準の連続記録の実行では、測定可能なパフォーマンスへの影響はありません。

オーバーヘッドの1つの大きな要因は、デフォルトで無効になっているヒープ統計イベントです。ヒープ統計を有効にすると、テスト実行の開始時と終了時にOldガベージ・コレクションがトリガーされます。これらのOld GCは、アプリケーションに追加の休止時間を与えるので、遅延を測定している場合または休止時間の影響を受けやすい環境では、ヒープ統計を有効にして実行しないでください。メモリー・リークをデバッグするには、ヒープ統計よりも優れたオプションとして、OldObjectSampleイベントおよびpath-to-gc-rootsオプションがあります。「フライト・レコーダを使用したメモリー・リークのデバッグ」を参照してください。

注意:

パフォーマンスのプロファイリングのユース・ケースでは、ヒープ統計は必要でない場合があります。

ボトルネックの検出

ボトルネックは、アプリケーションごとに異なります。一部のアプリケーションにとって、ボトルネックは、I/Oまたはネットワークの待機、スレッド間の同期、または実際のCPU使用率である場合があります。また、ガベージ・コレクションの回数がボトルネックである場合もあります。複数のボトルネックを持つアプリケーションもあります。

アプリケーションのボトルネックを検出する方法の1つは、フライト記録の次のイベントを調べることです。使用している記録テンプレートで、次のすべてのイベントが有効になっていることを確認します。

  • jdk.FileRead
  • jdk.FileWrite
  • jdk.SocketRead
  • jdk.SocketWrite
  • jdk.JavaErrorThrow
  • jdk.JavaExceptionThrow
  • jdk.JavaMonitorEnter
  • jdk.JavaMonitorWait
  • jdk.ThreadStart
  • jdk.ThreadEnd
  • jdk.ThreadSleep
  • jdk.ThreadPark

選択されたJavaアプリケーションのイベントはすべて、スレッド・ストール・イベントであるという重要なプロパティを持ちます。スレッド・ストールは、スレッドがイベント中にアプリケーションを実行していなかったことを示し、これらはすべて期間イベントです。期間イベントは、アプリケーションが実行されていなかった時間を測定します。

jfrツールを使用して、記録されたイベントを出力し、次の情報を探します。

  • jdk.JavaMonitorWaitイベントでは、スレッドがモニターの待機に費やした時間が表示されます。
  • jdk.ThreadSleepおよびjdk.ThreadParkイベントは、スレッドがスリープ状態または停止状態であることを示します。
  • 読取りおよび書込みイベントは、I/Oに費やされた時間を示します。

Javaアプリケーションの重要なスレッドがブロックされた状態で多くの時間を費やしている場合は、アプリケーションの重要なセクションがシングル・スレッドであり、それがボトルネックであることを意味しています。Javaアプリケーションがソケットの待機に多くの時間を費やしている場合、主なボトルネックは、ネットワーク内またはアプリケーションが通信する他のマシンにある可能性があります。Javaアプリケーションの重要なスレッドがアプリケーション・イベントを生成せずに多くの時間を費やしている場合、アプリケーションのボトルネックは、コードの実行に費やした時間、またはCPU自体です。これらの各ボトルネックは、フライト記録内でさらに調査できます。

注意:

ほとんどのJavaアプリケーション・イベント・タイプについて、20ミリ秒より長いイベントのみが記録されます。(このしきい値は、フライト記録を開始するときに変更できます。)要約すると、アプリケーションがファイルへの書込み(一度に一部分のみ)などの短いタスクを多数実行しているか、非常に短時間の同期に時間を費やしているので、領域には記録されたイベントがない可能性があります。

ガベージ・コレクションのパフォーマンス

フライト・レコーダからの記録は、ガベージ・コレクションに関するJavaアプリケーションの問題の診断に役立つ場合があります。

HotSpotガベージ・コレクタをチューニングすると、パフォーマンスに大きな影響を与える可能性があります。詳細は、『Java Platform, Standard Edition HotSpot仮想マシン・ガベージ・コレクション・チューニング・ガイド』ガベージ・コレクション・チューニングの概要に関する項を参照してください。

ガベージ・コレクションの問題を調査するには、実行中のアプリケーションのプロファイリング・フライト記録を取得します。ヒープ統計を含めると、追加のOldコレクションがトリガーされるので、ヒープ統計を含めないでください。よいサンプルを得るには、より長い記録を取ります(たとえば1時間)。

jfrツールを使用して、記録されたjdk.GCPhasePauseイベントを出力します。次の例は、イベントに含まれる情報を示しています。

c:\Program Files\Java\jdk-13\bin>jfr print --events jdk.GCPhasePause \
   gctest.jfr
jdk.GCPhasePause {
  startTime = 11:19:13.779
  duration = 3.419 ms
  gcId = 1
  name = "GC Pause"
  eventThread = "VM Thread" (osThreadId = 17528)
}

jdk.GCPhasePauseイベントからの情報を使用して、各GCの一時休止の平均合計、一時休止の最大合計、および一時休止時間の合計を計算できます。一時休止合計は、GC中にアプリケーションが一時休止した時間の合計です。多くのGCは、バックグラウンドでほとんどの処理を行います。この場合、GCの長さは重要ではなく、アプリケーションが実際に停止していた時間が重要です。したがって、一時休止合計は、GCの影響を適切に測定します。

ガベージ・コレクションの主なパフォーマンスの問題は、通常、個々のGCの時間がかかりすぎるか、GCの一時休止(GCの一時休止の合計)に多くの時間を費やしているかのいずれかです。

個々のGCに時間がかかりすぎる場合は、GCの戦略を変更する必要があります。一時休止の回数とスループット・パフォーマンスの対比となると、各GCには異なるトレードオフがあります。たとえば、ファイナライザまたは準参照の使用を減らすように、アプリケーションの修正が必要になる場合もあります。『Java Platform, Standard Edition HotSpot仮想マシン・ガベージ・コレクション・チューニング・ガイド』動作ベースのチューニングに関する項を参照してください。

アプリケーションが一時休止している時間が多すぎる場合は、様々な回避方法があります。

  • Javaヒープ・サイズを大きくしてください。Javaヒープが大きいほど、GC間の時間は長くなります。Javaアプリケーションにメモリー・リークがあると、GCがますます頻繁になり、最後にはOutOfMemoryErrorがスローされる可能性があるので、注意が必要です。詳細は、「フライト・レコーダによるメモリー・リークのデバッグ」を参照してください。

  • GCの数を減らすには、より少ない一時オブジェクトを割り当てます。小さなオブジェクトは「TLAB」内に割り当てられ、大きなオブジェクトは「TLAB」外に割り当てられています。多くの場合、割当ての大半は「TLAB」内で起こります。jdk.ObjectAllocationInNewTLABおよびjdk.ObjectAllocationOutsideTLABイベントは、一時オブジェクトの割当てに関する情報を提供します。

  • 必要なGCを減らすために割当て率を減らします。jdk.ThreadAllocationStatisticsイベントは、スレッドごとの割当てに関する情報を提供します。

これ以外のいくつかの設定により、JavaアプリケーションのGCのパフォーマンスが向上することもあります。GCパフォーマンスの詳細は、『Java Platform, Standard Edition HotSpot Virtual Machineガベージ・コレクション・チューニング・ガイド』「ガベージファースト・ガベージ・コレクタ」を参照してください。

同期パフォーマンス

Javaアプリケーションの同期の問題(アプリケーション・スレッドがモニターへの参加を長時間待機している)をデバッグするには、フライト・レコーダからの記録でjdk.JavaMonitorWaitイベントを見てください。

最も競合しているロック、およびロックの取得を待機しているスレッドのスタック・トレースを参照してください。通常、問題になると予想されていなかった競合を探します。ロギングは、一部のアプリケーションで予想外のボトルネックになる可能性がある共通領域です。

プログラムの更新後またはいずれかの特定の時間に、Javaアプリケーションのパフォーマンスの低下が見られる場合は、状況がよいときにフライト記録を取り、状況が悪いときに別のフライト記録を取って、大きく増加している同期サイトを探します。

注意:

デフォルトでは、期間が20ミリ秒より長い競合イベントが記録されます。このしきい値は、フライト記録を開始するときに変更できます。より短いしきい値は、より多くのイベントを与えますが、オーバーヘッドが増加する可能性があります。競合が問題であると考えている場合は、ほんの数ミリ秒の非常に低いしきい値で短い記録を取ることができます。これをライブ・アプリケーションで実行する場合は、非常に短い記録から開始し、パフォーマンス・オーバーヘッドを監視してください。

I/Oパフォーマンス

Javaアプリケーションがソケットまたはファイルの読取りと書込みに多くの時間を費やしている場合は、I/Oまたはネットワークがボトルネックである可能性があります。フライト・レコーダからの記録は、問題の領域の特定に役立つ場合があります。

アプリケーションのI/O問題を診断するには、フライト記録で次のイベントを参照してください。使用している記録テンプレートで、次のすべてのイベントが有効になっていることを確認します。

  • jdk.SocketWrite
  • jdk.SocketRead
  • jdk.FileWrite
  • jdk.FileRead

フライト記録のソケットの読取りおよび書込み情報を使用して、特定のリモート・アドレスからの読取り数、読取りの合計バイト数、および待機に費やした合計時間を計算します。各イベントを参照して、所要時間および読み取ったデータを分析します。

ファイルまたはネットワークI/Oの問題も同様の方法で診断します。読取り/書込みの最も多いファイルを調べ、各ファイルの読取り/書込みとI/Oにかかった時間を確認します。

デフォルトでは、期間が20ミリ秒より長いイベントのみが記録されます。フライト記録を開始するとき、より多くのデータを収集するために、ファイルI/Oのしきい値またはソケットI/Oのしきい値を下げることができますが、パフォーマンスへの影響が大きくなる可能性があります。

コード実行パフォーマンス

Javaアプリケーションのイベントが多くない場合、アプリケーションの主なボトルネックは、実行中のコードである可能性があります。フライト・レコーダからの記録は、問題の領域の特定に役立つ場合があります。

jdk.CPULoadイベントを参照し、一定期間にわたるCPU使用率を確認します。これは、記録されているJVMのCPU使用率とマシン上の総CPU使用率を示しています。JVMのCPU使用率は低いが、マシンのCPU使用率が高い場合は、他のアプリケーションが多くのCPUを消費している可能性があります。その場合は、Topやタスク・マネージャなどのOSツールを使用してシステムで実行している他のアプリケーションを調べ、CPUの使用率が高いプロセスを確認してください。

自分のアプリケーションがCPU時間を多く使用している場合は、jdk.ThreadCPULoadイベントを参照し、最もCPU時間を使用するスレッドを特定します。この情報はメソッド・サンプリングに基づいているので、サンプル数が少ない場合には100%正確でないことがあります。記録が実行されているとき、JVMはスレッドをサンプリングします。デフォルトでは、連続記録ではいくつかのメソッド・サンプリングのみが実行され、プロファイリング記録ではできるかぎり多く実行されます。メソッド・サンプリングでは、コードを実行しているスレッドだけからデータを収集します。I/O待ち、スリープ中、ロック待ちなどのスレッドはサンプリングされません。したがって、メソッド・サンプルが多いスレッドは、CPU時間を最も多く使用しているスレッドですが、各スレッドがどのくらいのCPUを使用しているかはわかりません。

「コード」タブ・グループの「ホット・メソッド」タブは、アプリケーションが実行時間の大半を費やしている場所を見つけるのに役立ちます。このタブには、スタックの上位メソッドによりグループ化されたすべてのサンプルが表示されます。「呼出しツリー」タブを使用して、スタック・トレースの最下位のメソッドから始め、上方に移動します。Thread.runから始めて、最も多くサンプリングされている呼出しを参照します。