メソッドコールの記録は、プロファイラにとって最も難しいタスクの一つです。なぜなら、相反する制約のもとで動作する必要があるからです。結果は正確かつ完全で、オーバーヘッドが非常に小さく、計測データから導き出される結論が誤ったものにならないようにしなければなりません。残念ながら、すべてのアプリケーションタイプに対してこれらの要件を満たす単一の計測方法は存在しません。そのため、JProfilerではどの方法を使用するかを選択する必要があります。
サンプリングとインストゥルメンテーションの比較
メソッドコールの計測には、「サンプリング」と「インストゥルメンテーション」という2つの根本的に異なる技術があります。それぞれに利点と欠点があります。サンプリングでは、スレッドの現在の呼び出しスタックを定期的に調査します。インストゥルメンテーションでは、選択したクラスのバイトコードを修正し、メソッドのエントリと終了をトレースします。インストゥルメンテーションはすべての呼び出しを計測でき、すべてのメソッドの呼び出し回数を取得できます。
サンプリングデータを処理する際、サンプリング期間全体(通常は5ms)がサンプリングされた呼び出しスタックに割り当てられます。サンプル数が多い場合、統計的に正しい全体像が得られます。サンプリングの利点は、実行頻度が低いためオーバーヘッドが非常に小さいことです。バイトコードを修正する必要がなく、サンプリング期間は通常のメソッドコールの実行時間よりも長くなります。一方で、メソッドの呼び出し回数を特定することはできません。また、数回しか呼び出されない短時間実行のメソッドはまったく表示されない場合があります。パフォーマンスのボトルネックを探している場合は問題ありませんが、コードの詳細な実行時特性を理解したい場合は不便です。
一方、インストゥルメンテーションは、多数の短時間実行メソッドをインストゥルメントすると大きなオーバーヘッドを生じる可能性があります。このインストゥルメンテーションは、時間計測自体のオーバーヘッドだけでなく、通常であればHotSpotコンパイラによってインライン化される多くのメソッドが、個別のメソッドコールとして残る必要があるため、パフォーマンスのホットスポットの相対的重要性を歪めてしまいます。長時間実行されるメソッドコールの場合、オーバーヘッドは無視できる程度です。主に高レベルな処理を行うクラスのみを選択できれば、インストゥルメンテーションのオーバーヘッドは非常に小さくなり、サンプリングよりも適している場合があります。JProfilerのオーバーヘッドホットスポット検出機能も、何度か実行することで状況を改善できます。また、呼び出し回数は重要な情報であり、何が起きているかを把握しやすくなります。
フルサンプリングと非同期サンプリング
JProfilerはサンプリングのために2つの異なる技術的ソリューションを提供しています。「フルサンプリング」は、JVM内のすべてのスレッドを定期的に一時停止し、スタックトレースを調査する専用スレッドによって実行されます。ただし、JVMは特定の「セーフポイント」でのみスレッドを停止するため、バイアスが生じます。高いマルチスレッド性かつCPUバウンドなコードの場合、プロファイルされたホットスポットの分布が歪む可能性があります。一方で、コードがI/Oも多く行う場合、このバイアスは一般的に問題になりません。
高度にCPUバウンドなコードで正確な数値を取得するために、JProfilerは非同期サンプリング(async sampling)も提供しています。非同期サンプリングでは、プロファイリング用のシグナルハンドラが実行中のスレッド自身で呼び出されます。プロファイリングエージェントはネイティブスタックを調査し、Javaスタックフレームを抽出します。主な利点は、このサンプリング方法ではセーフポイントバイアスがなく、高度にマルチスレッドかつCPUバウンドなアプリケーションでもオーバーヘッドが低いことです。ただし、CPUビューでは「Running」スレッド状態のみ観測可能で、「Waiting」「Blocking」「Net I/O」状態はこの方法では計測できません。プローブデータは常にバイトコードインストゥルメンテーションで収集されるため、JDBCなどのデータではすべてのスレッド状態が取得できます。
非同期サンプリングでは、呼び出しスタックの末尾のみが取得できるトレースの切り捨てが発生します。そのため、非同期サンプリングでは呼び出しツリーよりもホットスポットビューの方が有用な場合が多いです。非同期サンプリングはLinuxおよびmacOSのみサポートされています。
Java 17以降では、JProfilerはHotSpot JVMでサンプリング時にグローバルセーフポイントを使用せず、ほぼゼロオーバーヘッドでフルサンプリングを実行できます。非同期サンプリングと比較すると、単一スレッドに対しては依然としてセーフポイントバイアスが残りますが、JVM内のすべてのスレッドに対するグローバルセーフポイントのオーバーヘッドはなくなります。非同期サンプリングの欠点を考慮すると、Java 17以降ではフルサンプリングの利用が推奨されます。
メソッドコール記録タイプの選択
プロファイリングでどのメソッドコール記録タイプを使用するかは重要な決定事項であり、すべての状況において正解となる選択肢はありません。そのため、十分な情報をもとに判断する必要があります。新しいセッションを作成する際、セッション開始ダイアログでどのメソッドコール記録タイプを使用するか尋ねられます。後からでもセッション設定ダイアログでメソッドコール記録タイプを変更できます。
シンプルなガイドとして、アプリケーションがスペクトラムの両極端のいずれかに該当するかどうかを判断するため、次の質問を検討してください。
-
プロファイルされたアプリケーションはI/Oバウンドですか?
多くのWebアプリケーションは、ほとんどの時間をRESTサービスやJDBCデータベースコールの待機に費やしています。この場合、インストゥルメンテーションが最適な選択肢となりますが、呼び出しツリーフィルターを慎重に設定し、自分のコードのみを含めるようにしてください。 -
プロファイルされたアプリケーションは高いマルチスレッド性かつCPUバウンドですか?
例えば、コンパイラ、画像処理アプリケーション、または負荷テスト中のWebサーバーなどが該当します。LinuxまたはmacOSでプロファイリングする場合、このケースでは最も正確なCPU時間を得るために非同期サンプリングを選択してください。
それ以外の場合は、一般的に「フルサンプリング」が最も適しており、新規セッションのデフォルトとして推奨されます。
ネイティブサンプリング
非同期サンプリングはネイティブスタックにもアクセスできるため、ネイティブサンプリングも実行可能です。デフォルトでは、ネイティブサンプリングは有効になっていません。なぜなら、呼び出しツリーに大量のノードが追加され、ホットスポット計算の焦点がネイティブコードに移ってしまうためです。もしネイティブコードにパフォーマンス問題がある場合は、非同期サンプリングを選択し、セッション設定でネイティブサンプリングを有効にしてください。
JProfilerは、各ネイティブスタックフレームが属するライブラリのパスを解決します。呼び出しツリー内のネイティブメソッドノードでは、JProfilerはネイティブライブラリのファイル名を角括弧で囲んで先頭に表示します。
集約レベルに関しては、ネイティブライブラリはクラスのように扱われます。そのため、「クラス」集約レベルでは、同じネイティブライブラリ内の後続の呼び出しはすべて1つのノードに集約されます。「パッケージ」集約レベルでは、すべての後続のネイティブメソッドコールがネイティブライブラリに関係なく1つのノードに集約されます。
特定のネイティブライブラリを除外したい場合は、ノードを削除し、そのネイティブライブラリからクラス全体を削除することができます。







