JProfiler ヘルプ

メモリプロファイリング

ヒープ上のオブジェクトに関する情報を取得する方法は2つあります。1つは、プロファイリングエージェントが各オブジェクトの割り当てとガーベジコレクションをトラッキングする方法です。JProfilerでは、これを「割り当て記録」と呼びます。 これにより、どこでオブジェクトが割り当てられたかが分かり、一時オブジェクトに関する統計も作成できます。 もう1つは、JVMのプロファイリングインターフェースを利用してプロファイリングエージェントが「ヒープスナップショット」を取得し、 すべての生存オブジェクトとその参照関係を調査する方法です。この情報は、なぜオブジェクトがガーベジコレクションされないのかを理解するために必要です。

割り当て記録とヒープスナップショットはいずれもコストの高い操作です。割り当て記録は、 java.lang.Object コンストラクタをインストゥルメントし、ガーベジコレクタが継続的にプロファイリングインターフェースへ報告する必要があるため、 実行時の特性に大きな影響を与えます。そのため、割り当てはデフォルトでは記録されておらず、 記録の開始と停止を明示的に行う必要があります。 ヒープスナップショットの取得は一度きりの操作ですが、JVMを数秒間停止させることがあり、 取得したデータの解析もヒープサイズに比例して比較的長い時間がかかる場合があります。

JProfilerはメモリアナリシスを2つのビューセクションに分割しています。「ライブメモリ」セクションは定期的に更新可能なデータを表示し、 「ヒープウォーカー」セクションは静的なヒープスナップショットを表示します。 割り当て記録は「ライブメモリ」セクションで制御されますが、記録されたデータはヒープウォーカーでも表示されます。

メモリプロファイリングで解決できる最も一般的な3つの問題は、 メモリリークの発見、メモリ消費量の削減、 一時オブジェクトの生成削減です。最初の2つの問題では主にヒープウォーカーを使用し、 JVM内で最大のオブジェクトを保持している箇所や、その生成元を調べます。 最後の問題については、すでにガーベジコレクションされたオブジェクトが関与するため、 記録された割り当てを表示するライブビューのみが頼りになります。

インスタンス数のトラッキング

ヒープ上にどのようなオブジェクトが存在するかを把握するには、「すべてのオブジェクト」ビューで すべてのクラスとそのインスタンス数のヒストグラムが表示されます。 このビューのデータは割り当て記録で収集されるのではなく、インスタンス数のみを計算する ミニヒープスナップショットを実行することで取得されます。 ヒープが大きいほどこの操作に時間がかかるため、このビューは現在値で自動更新されません。

メモリリークを探す際には、インスタンス数を時間経過で比較したい場合がよくあります。 すべてのクラスについてそれを行うには、ビューの差分機能を利用できます。 2つのダンプを同時に選択すると、差分列が挿入され、インスタンス数のヒストグラムには マーク時点の基準値が緑色で表示されます。新たにダンプを取得すると、 最も古い選択済みダンプが選択されたままとなり、新しいダンプとの差分が表示されます。

ダンプセレクタにはダンプ取得時のタイムスタンプが表示されます。 ダンプをダブルクリックすると、識別しやすいようにラベルを追加できます。 すべてのオブジェクトのダンプは、トリガーアクションController APIからも実行でき、 その際にもラベルを指定できます。

一方、「記録済みオブジェクト」ビューは、割り当て記録開始後に割り当てられたオブジェクトのみのインスタンス数を表示します。 割り当て記録を停止すると新たな割り当ては追加されませんが、ガーベジコレクションのトラッキングは継続されます。 これにより、特定のユースケースでヒープ上に残るオブジェクトを確認できます。 オブジェクトが長時間ガーベジコレクションされない場合もあるので注意してください。 GCの実行ツールバーボタンを使うことで、このプロセスを促進できます。 動的に更新されるほとんどのビューと同様に、フリーズツールバーボタンで表示データの更新を停止できます。

現在をマークツールバーボタンを使うと、「記録済みオブジェクト」ビューでも選択した基準値との差分列を表示できます。 選択したクラスについては、コンテキストメニューの 選択をクラス・トラッカーに追加アクションで、時間推移のグラフも表示できます。

割り当てスポット

割り当て記録が有効な場合、JProfilerはオブジェクトが割り当てられるたびに呼び出しスタックを記録します。 ただし、stack-walking APIなどから正確な呼び出しスタックを取得するのは非常にコストが高いため、 CPUプロファイリングと同じ仕組みが使われます。 つまり、呼び出しスタックは呼び出しツリーフィルタに従ってフィルタされ、 実際の割り当てスポットが呼び出しスタックに現れない場合もあります(無視またはコンパクトフィルタされたクラスの場合)。 ただし、これらの違いは直感的に理解しやすく、コンパクトフィルタされたメソッドは、 その後のコンパクトフィルタクラスへの呼び出しで行われたすべての割り当てに責任を持ちます。

サンプリングを使用すると、割り当てスポットは近似的なものとなり、混乱する場合があります。 時間計測と異なり、特定のクラスがどこで割り当てられるかを明確に把握していることが多いですが、 サンプリングは統計的なイメージを描くため、java.util.HashMap.getが 自分のクラスを割り当てているような、一見あり得ない割り当てスポットが表示されることもあります。 正確な数値や呼び出しスタックが重要な分析では、インストゥルメンテーションと割り当て記録の併用を推奨します。

CPUプロファイリングと同様に、割り当て呼び出しスタックは呼び出しツリーとして表示されますが、 呼び出し回数や時間ではなく、割り当て回数と割り当て済みメモリが表示されます。 CPU呼び出しツリーと異なり、割り当て呼び出しツリーは自動的に表示・更新されません。 なぜならツリーの計算コストが高いためです。 JProfilerはすべてのオブジェクトだけでなく、選択したクラスやパッケージについても割り当てツリーを表示できます。 その他のオプションとあわせて、現在のデータから割り当てツリーを計算するようJProfilerに指示した後に表示されるオプションダイアログで設定できます。

CPU呼び出しツリーの有用な特性は、上から下へ累積時間を追えることです。各ノードには子ノードで消費された時間が含まれます。 デフォルトでは、割り当てツリーも同様に動作し、各ノードには子ノードで行われた割り当てが含まれます。 割り当てが呼び出しツリーの深いリーフノードでのみ行われていても、その数値は上位ノードへ伝播します。 これにより、割り当て呼び出しツリーの枝を展開する際、どのパスを調査すべきか常に把握できます。 「自己割り当て」は、実際にそのノード自身で行われた割り当てで、子孫ノードによるものではありません。 CPU呼び出しツリーと同様に、パーセンテージバーで色分けして表示されます。

割り当て呼び出しツリーでは、特に選択したクラスの割り当てを表示する場合、 割り当てが全く行われていないノードが多数存在することがあります。 これらのノードは、実際に割り当てが行われたノードに至る呼び出しスタックを示すためだけに存在します。 JProfilerではこのようなノードを「ブリッジ」ノードと呼び、上記スクリーンショットのようにグレーのアイコンで表示します。 場合によっては割り当ての累積が邪魔になることもあり、実際の割り当てスポットだけを見たい場合もあります。 割り当てツリーのビュー設定ダイアログには、そのための非累積表示オプションがあります。 有効化すると、ブリッジノードは常に割り当て数ゼロとなり、パーセンテージバーも表示されません。

割り当てホットスポットビューは、割り当て呼び出しツリーとともに生成され、 選択したクラスの生成に関与するメソッドに直接フォーカスできます。 記録済みオブジェクトビューと同様に、割り当てホットスポットビューでも現在の状態をマークし、 時間経過での変化を観察できます。差分列が追加され、現在値をマークアクション実行時から ホットスポットがどれだけ変化したかが表示されます。 割り当てビューはデフォルトで定期更新されないため、新しいデータセットを取得するには 計算ツールバーボタンをクリックし、基準値と比較します。 オプションダイアログで自動更新も設定できますが、大きなヒープサイズでは推奨されません。

割り当て記録レート

すべての割り当てを記録すると大きなオーバーヘッドが発生します。 多くの場合、割り当ての総数は重要ではなく、相対的な数値で十分問題解決できます。 そのため、JProfilerはデフォルトで10回に1回だけ割り当てを記録します。 これにより、すべての割り当てを記録する場合と比べてオーバーヘッドは約1/10に抑えられます。 すべての割り当てを記録したい場合や、さらに少ない割り当てで十分な場合は、 記録済みオブジェクトビューや割り当て呼び出しツリー・ホットスポットビューのパラメータダイアログで 記録レートを変更できます。

この設定は、セッション設定ダイアログの「詳細設定→メモリプロファイリング」ステップでも見つかり、 オフラインプロファイリングセッション用に調整できます。

割り当て記録レートは、「記録済みオブジェクト」および「記録済みスループット」のVMテレメトリーに影響し、 これらの値は設定された割合で計測されます。 スナップショットを比較する際、最初のスナップショットの割り当てレートが報告され、 他のスナップショットは必要に応じてスケーリングされます。

割り当て済みクラスの分析

割り当てツリーや割り当てホットスポットビューを計算する際、 事前に割り当てを見たいクラスやパッケージを指定する必要があります。 すでに特定のクラスに注目している場合は問題ありませんが、 事前知識なしに割り当てホットスポットを探したい場合は不便です。 1つの方法は、「記録済みオブジェクト」ビューからコンテキストメニューのアクションを使い、 選択したクラスやパッケージの割り当てツリーや割り当てホットスポットビューに切り替えることです。

もう1つの方法は、すべてのクラスについて割り当てツリーや割り当てホットスポットを表示し、 クラスを表示アクションで、選択した割り当てスポットや割り当てホットスポットのクラスを表示することです。

割り当て済みクラスのヒストグラムは、 呼び出しツリー分析として表示されます。 このアクションは他の呼び出しツリー分析からも利用できます。

クラス分析ビューは静的であり、割り当てツリーやホットスポットビューが再計算されても自動更新されません。 分析を再読み込みアクションを実行すると、まず割り当てツリーが更新され、 その新しいデータから現在の分析ビューが再計算されます。

ガーベジコレクションされたオブジェクトの分析

割り当て記録は、生存オブジェクトだけでなく、ガーベジコレクションされたオブジェクトの情報も保持できます。 これは一時的な割り当てを調査する際に有用です。 多数の一時オブジェクトを割り当てると大きなオーバーヘッドが発生するため、 割り当てレートを減らすことでパフォーマンスが大幅に向上することがあります。

記録済みオブジェクトビューでガーベジコレクション済みオブジェクトを表示するには、 生存性セレクタをガーベジコレクション済みオブジェクトまたは 生存+ガーベジコレクション済みオブジェクトに変更します。 割り当て呼び出しツリーや割り当てホットスポットビューのオプションダイアログにも同様のドロップダウンがあります。

ただし、JProfilerはデフォルトでガーベジコレクション済みオブジェクトの割り当てツリー情報を収集しません。 なぜなら、生存オブジェクトのみのデータであれば、はるかに低いオーバーヘッドで維持できるためです。 「割り当て呼び出しツリー」や「割り当てホットスポット」ビューで生存性セレクタを ガーベジコレクション済みオブジェクトを含むモードに切り替えると、 JProfilerは記録タイプの変更を提案します。 これはプロファイリング設定の変更となるため、すぐに変更を適用すると それまでに記録されたデータはすべてクリアされます。 事前にこの設定を変更したい場合は、セッション設定ダイアログの「詳細設定」→「メモリプロファイリング」で行えます。

次のステップ:ヒープウォーカー

より高度な分析では、オブジェクト間の参照関係が関わってきます。 例えば、記録済みオブジェクト、割り当てツリー、割り当てホットスポットビューで表示されるサイズは シャローサイズです。 これはクラスのメモリレイアウトのみを含み、参照先のクラスは含みません。 クラスのオブジェクトが実際にどれだけ重いかを知るには、 保持サイズ(そのオブジェクトをヒープから削除した場合に解放されるメモリ量)を知る必要があります。

この種の情報はライブメモリビューでは取得できません。なぜなら、 すべてのヒープ上のオブジェクトを列挙し、コストの高い計算を行う必要があるためです。 この作業はヒープウォーカーが担当します。 ライブメモリビューの関心のあるポイントからヒープウォーカーにジャンプするには、 ヒープウォーカーで表示ツールバーボタンを使用します。 これにより、ヒープウォーカーの対応するビューに移動します。

ヒープスナップショットが存在しない場合は新たに作成され、 既存のヒープスナップショットがある場合は、JProfilerが利用するかどうかを確認します。

いずれの場合も、ライブメモリビューとヒープウォーカーで表示される数値は大きく異なることが多い点に注意が必要です。 ヒープウォーカーはライブメモリビューとは異なる時点のスナップショットを表示するだけでなく、 参照されていないすべてのオブジェクトを除外します。 ガーベジコレクタの状態によっては、参照されていないオブジェクトがヒープの大部分を占めていることもあります。