CPUおよびメモリプロファイリングは主にオブジェクトやメソッドコールといった、JVM上のアプリケーションの基本的な構成要素に注目します。しかし、いくつかのテクノロジーでは、実行中のアプリケーションからセマンティックなデータを抽出し、プロファイラで表示するような、より高レベルなアプローチが必要となります。
その代表的な例が、JDBCを使ったデータベースへの呼び出しのプロファイリングです。呼び出しツリーでは、JDBC APIをいつ利用したか、またその呼び出しにどれだけ時間がかかったかを表示します。しかし、各呼び出しで実行されるSQL文は異なる場合があり、どの呼び出しがパフォーマンスのボトルネックになっているのか分かりません。また、JDBCの呼び出しはアプリケーション内の様々な場所から発生するため、汎用的な呼び出しツリー内から探すのではなく、全てのデータベース呼び出しを一つのビューでまとめて確認できることが重要です。
この問題を解決するために、JProfilerはJREの重要なサブシステム向けに複数のプローブを提供しています。プローブは特定のクラスにインストゥルメンテーションを追加し、データを収集して「Databases」や「JEE & Probes」ビューセクションなど専用のビューで表示します。さらに、プローブは呼び出しツリーにデータを注釈として追加できるため、汎用的なCPUプロファイリングと高レベルなデータの両方を同時に確認できます。
JProfilerが直接サポートしていないテクノロジーについてさらに情報を取得したい場合は、独自のプローブを作成することができます。また、いくつかのライブラリ、コンテナ、またはデータベースドライバは、埋め込みプローブを同梱しており、アプリケーションで利用されるとJProfiler上で表示されます。
プローブイベント
プローブはオーバーヘッドを追加するため、デフォルトでは記録されません。各プローブごとに手動または自動で、個別に記録を開始する必要があります。
プローブの機能によって、プローブデータは複数のビューで表示されます。最も低レベルなのがプローブイベントです。他のビューでは、プローブイベントを集約したデータが表示されます。デフォルトでは、プローブイベントはプローブが記録されていても保持されません。単一イベントが重要な場合は、プローブイベントビューで記録できます。ファイルプローブのように、通常イベント発生頻度が高いプローブでは、単一イベントの記録は推奨されません。一方、「HTTP server」プローブやJDBCプローブのように、イベント発生頻度が低い場合は、単一イベントの記録が適しています。
プローブイベントは、メソッドパラメータ、戻り値、インストゥルメントされたオブジェクト、スローされた例外など、さまざまなソースからプローブ文字列を取得します。プローブは複数のメソッドコールからデータを収集する場合もあり、例えばJDBCプローブは、実際のSQL文字列を構築するためにプリペアドステートメントの全てのsetter呼び出しをフックする必要があります。プローブ文字列は、プローブが測定する高レベルサブシステムに関する基本情報です。さらに、イベントには開始時刻、オプションの継続時間、関連スレッド、スタックトレースが含まれます。
テーブルの一番下には、表示されているイベントの合計数や、テーブル内の全ての数値列の合計を示す特別な行があります。デフォルトの列では、これはDuration列のみが対象です。テーブル上部のフィルタセレクタと組み合わせて、選択したイベントのサブセットに対して収集データを分析できます。デフォルトでは、テキストフィルタは全てのテキストフィールド列に適用されますが、テキストフィールドの前のドロップダウンから特定のフィルタ列を選択できます。フィルタオプションはコンテキストメニューからも利用でき、例えば選択したイベントよりも長い継続時間を持つ全てのイベントをフィルタすることができます。
他のプローブビューでもプローブイベントのフィルタオプションが用意されています。プローブテレメトリービューでは時間範囲を選択でき、プローブ呼び出しツリービューでは選択した呼び出しスタックからイベントをフィルタできます。プローブホットスポットビューでは、選択したバックトレースやホットスポットに基づいたプローブイベントフィルタが提供され、制御オブジェクトやタイムラインビューでは、選択した制御オブジェクトに対するプローブイベントのフィルタアクションが利用できます。
選択したプローブイベントのスタックトレースは下部に表示されます。複数のプローブイベントを選択した場合、スタックトレースは集約され、呼び出しツリー、プローブホットスポット(バックトレース付き)、またはCPUホットスポット(バックトレース付き)として表示されます。
スタックトレースビューの隣には、イベント継続時間や、オプションで記録されたスループットのヒストグラムビューが表示されます。これらのヒストグラムでマウスを使って継続時間の範囲を選択し、上部のテーブルでプローブイベントをフィルタできます。
プローブは様々な種類のアクティビティを記録し、それぞれのプローブイベントにイベントタイプを関連付けることができます。例えば、JDBCプローブでは、ステートメント、プリペアドステートメント、バッチ実行を異なる色のイベントタイプとして表示します。
単一イベントを記録する際のメモリ使用量の増加を防ぐため、JProfilerはイベントを統合します。イベントの上限はプロファイリング設定で構成され、全てのプローブに適用されます。最新のイベントのみが保持され、古いイベントは破棄されます。この統合は高レベルのビューには影響しません。
プローブ呼び出しツリーとホットスポット
プローブの記録はCPU記録と密接に連携します。プローブイベントはプローブ呼び出しツリーに集約され、プローブ文字列がリーフノード(「ペイロード」)となります。プローブイベントが作成された呼び出しスタックのみがそのツリーに含まれます。メソッドノードの情報は記録されたペイロード名に対応します。例えば、特定の呼び出しスタックでSQL文が42回実行され、合計9000msかかった場合、その呼び出しツリーの全ての祖先ノードにイベント数42と時間9000msが加算されます。全ての記録されたペイロードの累積が、どの呼び出し経路がプローブ固有の時間を最も消費しているかを示す呼び出しツリーを形成します。プローブツリーの焦点はペイロードにあるため、ビューのフィルタはデフォルトでペイロードを検索しますが、コンテキストメニューからクラスでフィルタするモードも選択できます。
CPU記録がオフの場合、バックトレースには「No CPU data was recorded」というノードのみが含まれます。CPUデータが一部しか記録されていない場合、これらのノードと実際のバックトレースが混在することがあります。サンプリングが有効な場合でも、JProfilerはデフォルトでプローブペイロードの正確な呼び出しトレースを記録します。このオーバーヘッドを避けたい場合は、プロファイリング設定でオフにできます。プローブ記録には他にもデータ収集量やオーバーヘッドを調整するためのチューニングオプションがいくつか用意されています。
ホットスポットはプローブ呼び出しツリーから計算できます。ホットスポットノードはメソッドコールではなくペイロードとなります(CPUビューセクションとは異なります)。これは多くの場合、プローブの最も即時的に役立つビューです。CPU記録が有効な場合は、トップレベルのホットスポットを展開してメソッドバックトレースを分析できます。バックトレースノードの数値は、最も深いノードからホットスポット直下のノードまでの呼び出しスタック上で、どれだけのプローブイベントがどれだけの合計時間で測定されたかを示します。
プローブ呼び出しツリーおよびプローブホットスポットビューの両方で、スレッドやスレッドグループ、スレッドステータス、メソッドノードの集約レベルを選択できます(CPUビューと同様)。CPUビューからデータを比較する場合、プローブビューのデフォルトのスレッドステータスが「全ての状態(All states)」であり、CPUビューの「Runnable」とは異なることに注意してください。これは、プローブイベントがデータベース呼び出しやsocket操作、プロセス実行など外部システムを伴うことが多く、JVMが実際に処理している時間だけでなく、全体の時間を見ることが重要だからです。
制御オブジェクト
外部リソースへのアクセスを提供する多くのライブラリでは、リソースとやり取りするためのコネクションオブジェクトが提供されます。例えば、プロセスを開始する場合、java.lang.Processオブジェクトを使って出力ストリームから読み込み、入力ストリームに書き込むことができます。JDBCを利用する場合、SQLクエリを実行するためにjava.sql.Connectionオブジェクトが必要です。JProfilerでは、このようなオブジェクトを「制御オブジェクト」と呼びます。
プローブイベントを制御オブジェクトごとにグループ化し、そのライフサイクルを表示することで、問題の発生箇所をより明確に把握できます。また、制御オブジェクトの生成はコストが高い場合が多いため、アプリケーションが過剰に生成したり、適切にクローズしていないことがないようにする必要があります。この目的のため、制御オブジェクトをサポートするプローブには「タイムライン」および「制御オブジェクト」ビューがあり、後者は例えばJDBCプローブでは「Connections」といったより具体的な名称になる場合もあります。制御オブジェクトがオープンまたはクローズされると、プローブは特別なプローブイベントを生成し、イベントビューで関連するスタックトレースを確認できます。
タイムラインビューでは、各制御オブジェクトがバーとして表示され、そのカラー化で制御オブジェクトがアクティブだった期間を示します。プローブは異なるイベントタイプを記録でき、タイムラインはそれに応じて色分けされます。このステータス情報はイベントリストから取得されるのではなく、100msごとに最新のステータスからサンプリングされます。制御オブジェクトには識別用の名前が付けられます。例えば、ファイルプローブではファイル名が制御オブジェクト名となり、JDBCプローブではコネクション文字列が制御オブジェクト名として表示されます。
制御オブジェクトビューでは、全ての制御オブジェクトが表形式で表示されます。デフォルトではオープン・クローズ両方の制御オブジェクトが表示されます。上部のコントロールを使って、オープンまたはクローズ済みの制御オブジェクトのみ表示したり、特定の列の内容でフィルタできます。制御オブジェクトの基本的なライフサイクルデータに加え、各制御オブジェクトの累積アクティビティ(例:イベント数や平均イベント継続時間)もテーブルに表示されます。
プローブごとに表示される列は異なり、例えばプロセスプローブでは読み込み・書き込みイベント用の別々の列セットが表示されます。この情報は単一イベント記録が無効でも利用できます。イベントビューと同様、下部の合計行をフィルタと組み合わせて使うことで、制御オブジェクトの部分集合に対する累積データを取得できます。
プローブは、特定のプロパティをネストされたテーブルで公開することができます。これは、メインテーブルの情報過多を防ぎ、列スペースを確保するためです。ファイルプローブやプロセスプローブのようにネストテーブルがある場合、各行の左側に展開ハンドルが表示され、その場でプロパティ値テーブルを開くことができます。
タイムライン、制御オブジェクトビュー、イベントビューはナビゲーションアクションで連携しています。例えば、タイムラインビューで行を右クリックし、他のビューにジャンプすることで、選択した制御オブジェクトのデータのみを表示できます。これは制御オブジェクトIDでフィルタすることで実現されています。
テレメトリーとトラッカー
プローブによって収集された累積データから、複数のテレメトリーが記録されます。全てのプローブで、1秒あたりのプローブイベント数や、平均継続時間、I/O操作のスループットなどの平均値が利用できます。制御オブジェクトを持つプローブでは、オープン中の制御オブジェクト数も標準的なテレメトリーとなります。各プローブは追加のテレメトリーを追加でき、例えばJPAプローブではクエリ数やエンティティ操作数のテレメトリーが個別に表示されます。
ホットスポットビューや制御オブジェクトビューでは、時間経過で追跡したい累積データが表示されます。これらの特別なテレメトリーはプローブトラッカーで記録されます。トラッキングの最も簡単な方法は、ホットスポットまたは制御オブジェクトビューから選択項目をトラッカーに追加アクションで新しいテレメトリーを追加することです。いずれの場合も、時間またはカウントのどちらをトラッキングするか選択します。制御オブジェクトをトラッキングする場合、テレメトリーは全てのプローブイベントタイプごとに積み上げ面グラフとして表示されます。ホットスポットをトラッキングする場合、トラッキングされた時間は異なるスレッド状態ごとに分割されます。
プローブテレメトリーは「テレメトリー」セクションに追加して、システムテレメトリーやカスタムテレメトリーと比較できます。また、テレメトリー概要のコンテキストメニューアクションでプローブ記録を制御することもできます。
JDBCとJPA
JDBCプローブとJPAプローブは連携して動作します。JPAプローブのイベントビューでは、JDBCプローブも記録されていれば、単一イベントを展開して関連するJDBCイベントを確認できます。
同様に、ホットスポットビューでは全てのホットスポットに「JDBC calls」ノードが追加され、JPA操作によってトリガーされたJDBC呼び出しが含まれます。JPA操作の中には非同期で即時実行されず、セッションがフラッシュされたタイミングで任意の時点で実行されるものもあります。パフォーマンス問題を調査する際、そのフラッシュのスタックトレースは有用ではないため、JProfilerは既存エンティティの取得や新規エンティティの永続化時のスタックトレースを記憶し、それをプローブイベントに紐付けます。その場合、ホットスポットのバックトレースは「Deferred operations」ノード内に含まれ、それ以外の場合は「Direct operations」ノードが挿入されます。
MongoDBプローブのような他のプローブでも、直接操作と非同期操作の両方をサポートしています。非同期操作は現在のスレッドではなく、同じJVM内の他のスレッドや別プロセスで実行されます。このようなプローブでは、ホットスポットのバックトレースが「Direct operations」と「Async operation」コンテナノードに分類されます。
JDBCプローブで特に注意すべき問題は、リテラルデータ(IDなど)がSQL文字列に含まれていると、良いホットスポットが得られないことです。プリペアドステートメントを使っていれば自動的にこの問題は回避されますが、通常のステートメントを実行している場合は、ほとんどのクエリが一度しか実行されないホットスポットリストとなる可能性があります。その対策として、JProfilerはJDBCプローブ設定で未準備ステートメントのリテラルを置換する非デフォルトオプションを提供しています。デバッグ目的でイベントビューにリテラルを表示したい場合もあるでしょうが、そのオプションを無効にすると、JProfilerが異なる文字列を多数キャッシュする必要がなくなるため、メモリオーバーヘッドを削減できます。
一方で、JProfilerはプリペアドステートメントのパラメータを収集し、プレースホルダなしの完全なSQL文字列をイベントビューに表示します。これもデバッグ時には有用ですが、不要な場合はプローブ設定でオフにしてメモリを節約できます。
JDBC接続リーク
JDBCプローブには「接続リーク」ビューがあり、データベースプールに返却されていないオープン中の仮想データベース接続を表示します。これは、プールされたデータベースソースによって作成された仮想接続のみに影響します。仮想接続はクローズされるまで物理接続をブロックします。
リーク候補には「unclosed」接続と「unclosed
collected」接続の2種類があります。どちらも、データベースプールから払い出された接続オブジェクトがまだヒープ上にあり、close()が呼ばれていない仮想接続です。「unclosed
collected」接続はガーベジコレクションされており、確定的な接続リークです。
「unclosed」接続オブジェクトはまだヒープ上にあります。Open Since継続時間が長いほど、その仮想接続がリーク候補である可能性が高くなります。仮想接続は10秒以上オープン状態の場合、リーク候補と見なされます。ただし、close()がその後呼ばれれば、「接続リーク」ビューからエントリは削除されます。
接続リークテーブルにはClass Name列があり、接続クラス名が表示されます。これにより、どのプールが接続を作成したかが分かります。JProfilerは多くのデータベースドライバや接続プールを明示的にサポートしており、仮想接続と物理接続のクラスを認識しています。不明なプールやドライバの場合、JProfilerが物理接続を仮想接続と誤認することがあります。物理接続は長寿命なことが多いため、その場合「接続リーク」ビューに表示されます。この場合、接続オブジェクトのクラス名を確認することで誤検知であることを特定できます。
デフォルトでは、プローブ記録を開始しても接続リーク分析は有効になりません。接続リークビューには個別の記録ボタンがあり、その状態はJDBCプローブ設定の接続リーク分析用にオープン中の仮想接続を記録チェックボックスと連動しています。イベント記録と同様、ボタンの状態は永続化されるため、一度分析を開始すれば、次回のプローブ記録セッションでも自動的に開始されます。
呼び出しツリー内のペイロードデータ
CPU呼び出しツリーを見る際、どこでプローブがペイロードデータを記録したかを確認するのは興味深いことです。そのデータは測定されたCPU時間の解釈に役立つ場合があります。そのため、多くのプローブはCPU呼び出しツリーにクロスリンクを追加します。例えば、クラスローダープローブはクラスロードがトリガーされた箇所を表示できます。これは呼び出しツリー上では通常見えないため、予期しないオーバーヘッドの原因となることがあります。呼び出しツリービューで不透明なデータベース呼び出しも、対応するプローブでワンクリックで詳細分析できます。さらに、呼び出しツリー分析でプローブリンクをクリックすると、自動的にプローブ呼び出しツリービューのコンテキストで分析が繰り返されます。
もう一つの方法は、ペイロード情報をCPU呼び出しツリー内に直接インライン表示することです。該当する全てのプローブには、そのための呼び出しツリーに注釈オプションが設定に用意されています。この場合、プローブ呼び出しツリーへのリンクは利用できません。各プローブは独自のペイロードコンテナノードを持ちます。同じペイロード名のイベントは集約され、呼び出し回数と合計時間が表示されます。ペイロード名は呼び出しスタックごとに統合され、古いエントリは「[earlier calls]」ノードに集約されます。呼び出しスタックごとの記録ペイロード名の最大数はプロファイリング設定で構成可能です。
呼び出しツリーの分割
一部のプローブは、プローブ文字列を使ってペイロードデータを呼び出しツリーに注釈するのではなく、各プローブ文字列ごとに呼び出しツリーを分割します。これは特にサーバー型プローブで有用で、異なる種類のリクエストごとに呼び出しツリーを個別に確認したい場合に役立ちます。「HTTP server」プローブはURLをフックし、URLのどの部分を呼び出しツリー分割に使うかを細かく制御できます。デフォルトでは、リクエストURIパスのみ(パラメータなし)が使用されます。
より柔軟にするため、分割文字列を決定するスクリプトを定義できます。スクリプト内では、現在のjavax.servlet.http.HttpServletRequestをパラメータとして受け取り、希望する文字列を返します。
さらに、分割レベルは1つに限定されず、複数のネストした分割を定義できます。例えば、最初にリクエストURIパスで分割し、その後HTTPセッションオブジェクトから抽出したユーザー名で分割することもできます。または、リクエストメソッドでグループ化してからリクエストURIで分割することも可能です。
ネストした分割を利用することで、呼び出しツリーの各レベルごとに個別のデータを確認できます。呼び出しツリーを見ていて、あるレベルが邪魔になる場合、「HTTP server」プローブ設定からそのレベルを除外したくなることがありますが、より便利に、記録済みデータを失うことなく、呼び出しツリー上で対応する分割ノードのコンテキストメニューから分割レベルを一時的に統合・分離できます。
呼び出しツリーの分割はメモリオーバーヘッドを大きくする可能性があるため、慎重に利用する必要があります。メモリ過負荷を防ぐため、JProfilerは分割の最大数を上限として制限します。特定の分割レベルで上限に達した場合、特別な「[capped nodes]」分割ノードが追加され、キャップカウンタをリセットするためのハイパーリンクが表示されます。デフォルトの上限が用途に対して低すぎる場合は、プロファイリング設定で増やすことができます。















































