JProfiler ヘルプ

メモリリークの検出

ヒープウォーカー機能の詳細

通常のメモリ使用量とメモリリークを区別することは、多くの場合それほど単純ではありません。しかし、過剰なメモリ使用量とメモリリークはどちらも同じ兆候を示すため、同様の方法で分析できます。分析は2つのステップで進められます。疑わしいオブジェクトを特定すること、そしてそれらのオブジェクトがなぜまだヒープ上に残っているのかを突き止めることです。

新しいオブジェクトの検出

メモリリークのあるアプリケーションが実行されていると、時間の経過とともに消費するメモリがますます増加します。メモリ使用量の増加を検出するには、VMテレメトリー、および「All objects」と「Recorded objects」ビューの差分比較機能を使用するのが最適です。これらのビューを使用すると、問題が発生しているかどうか、またその深刻度を判断できます。クラスヒストグラムテーブルの差分カラムを確認するだけで、何が問題であるか見当がつくこともあります。

メモリリークをより深く分析するには、ヒープウォーカーの機能が必要です。特定のユースケースに関するメモリリークを詳細に調査するには、「Mark heap」機能が最適です。これにより、過去の特定の時点以降にヒープ上に残っている新しいオブジェクトを識別できます。これらのオブジェクトについては、正当にヒープ上に残っているのか、それともオブジェクトとしての目的が既に終了しているにもかかわらず不要な参照によって生存し続けているのかを確認する必要があります。

関心のあるオブジェクトのセットを分離するもう1つの方法は、割り当て記録を利用することです。ヒープスナップショットを取得する際、記録されたすべてのオブジェクトを表示するオプションがあります。ただし、割り当て記録を特定のユースケースだけに限定したくない場合もあります。また、割り当て記録はオーバーヘッドが大きいため、Mark Heapアクションの方が比較してはるかに影響が少なくなります。さらに、ヒープをマークしている場合、ヒープウォーカーではヘッダーのUse newおよびUse oldハイパーリンクを使用して、任意の選択ステップで新旧のオブジェクトを選択できます。

最大オブジェクトの分析

メモリリークによって利用可能なヒープが満たされると、プロファイルされたアプリケーションにおける他の種類のメモリ使用量がかすんでしまうほどになります。その場合、新しいオブジェクトを調べる必要はなく、どのオブジェクトが最も重要(容量を消費している)かを分析するだけで済みます。

メモリリークの進行速度が非常に遅く、長期間にわたって顕在化しないこともあります。そのようなメモリリークが可視化されるまでプロファイリングし続けるのは現実的でない場合があります。OutOfMemoryErrorがスローされたときに自動的にHPROFスナップショットを保存するJVMの組み込み機能を使用すれば、通常のメモリ消費よりもメモリリークが支配的になっているスナップショットを取得できます。実際、本番システムのVMパラメータに常に

-XX:+HeapDumpOnOutOfMemoryError

を追加しておくことをお勧めします。これにより、開発環境では再現が難しいメモリリークを分析する手段を確保できます。

メモリリークが支配的である場合、ヒープウォーカーの「Biggest objects」ビューの上位オブジェクトに、誤って保持されたメモリが含まれています。最大オブジェクト自体は正当なオブジェクトである可能性がありますが、そのドミネーターツリーを開くことでリークしているオブジェクトにたどり着くことができます。単純なケースでは、1つのオブジェクトがヒープの大部分を占めています。例えば、オブジェクトをキャッシュするためにMapが使用され、そのキャッシュが決してクリアされない場合、そのMapは最大オブジェクトのドミネーターツリーに表示されます。

ガーベッジコレクタールートからの強参照チェーンの検索

オブジェクトが問題となるのは、それが強参照(strongly referenced)されている場合のみです。「強参照されている」とは、ガーベッジコレクタールートからそのオブジェクトへの参照チェーンが少なくとも1つ存在することを意味します。「ガーベッジコレクタールート」(略してGCルート)は、ガーベッジコレクターが認識しているJVM内の特殊な参照です。

GCルートからの参照チェーンを見つけるには、「Incoming references」ビューまたはヒープウォーカーグラフにあるShow Paths To GC Rootアクションを使用できます。このような参照チェーンは実際には非常に長くなることがあるため、通常は「Incoming references」ビューの方が解釈しやすいでしょう。参照は下からトップレベルのオブジェクトに向かって示されます。検索結果である参照チェーンのみが展開され、同じレベルにある他の参照は、ノードを閉じて再度開くか、コンテキストメニューのShow All Incoming Referencesアクションを呼び出すまで表示されません。

参照ノードで使用されているGCルートの種類やその他の用語の説明を確認するには、ツリー凡例を使用してください。

ツリー内のノードを選択すると、モードレスなツリー凡例ダイアログで、選択したノードで使用されているすべてのアイコンと用語がハイライト表示されます。ダイアログの行をクリックすると、下部に説明が表示されます。

ガーベッジコレクタールートの重要な種類としては、スタックからの参照、JNIを介してネイティブコードによって作成された参照、現在使用されている生存スレッドやオブジェクトmonitorなどのリソースがあります。さらにJVMは、重要なシステムを維持するためにいくつかの「sticky」参照を追加します。

クラスとクラスローダーには、特殊な循環参照スキームがあります。クラスは、以下の場合にそのクラスローダーと一緒にガーベッジコレクションされます。

  • そのクラスローダーによってロードされたどのクラスにも生存インスタンスが存在しない
  • クラスローダー自体が、自身のクラス以外から参照されていない
  • どのjava.lang.Classオブジェクトも、そのクラスローダーのコンテキスト以外から参照されていない

ほとんどの状況において、クラスは関心のあるGCルートへのパスにおける最後のステップとなります。クラス自体はGCルートではありません。しかし、カスタムクラスローダーが使用されていないすべての状況において、それらをGCルートとして扱うことは適切です。これはガーベッジコレクタールートを検索する際のJProfilerのデフォルトモードですが、パス・トゥ・ルート(path to root)オプションダイアログで変更できます。

GCルートへの最短パスの解釈が困難な場合は、追加のパスを検索できます。ただし、GCルートへのすべてのパスを検索することは、膨大な数のパスが生成される可能性があるため、通常はお勧めしません。

ライブメモリビューとは異なり、ヒープウォーカーは参照されていないオブジェクトを表示しません。ただし、ヒープウォーカーは強参照されているオブジェクトのみを表示するわけでもありません。デフォルトでは、ヒープウォーカーはソフト参照によってのみ参照されているオブジェクトも保持しますが、弱参照、ファントム参照、またはファイナライザ参照によってのみ参照されているオブジェクトは除外します。ソフト参照はヒープが枯渇しない限りガーベッジコレクションされないため、これらを含めないとヒープの大量使用を説明できない可能性があるからです。ヒープスナップショットを取得する際に表示されるオプションダイアログで、この動作を調整できます。

デバッグ目的では、弱参照されているオブジェクトをヒープウォーカー内に残しておくことが有用な場合があります。後から弱参照によって保持されているオブジェクトを削除したい場合は、「Remove objects retained by weak references」インスペクションを使用できます。

GCルートへのパスを検索する際、ヒープウォーカーのオプションダイアログでオブジェクトを保持するために選択された参照タイプが考慮されます。これにより、GCルートへのパス検索では、オブジェクトがヒープウォーカーに保持された理由を常に説明できます。GCルートへのパス検索のオプションダイアログでは、許容する参照タイプをすべての弱参照へと広げることができます。

オブジェクトセット全体の排除

ここまでは単一のオブジェクトのみを見てきました。多くの場合、メモリリークの一部となっている同じ型のオブジェクトが多数存在します。多くの場合、単一のオブジェクトに対する分析は、現在のオブジェクトセットに含まれる他のオブジェクトにも当てはまります。関心のあるオブジェクトが異なる方法で参照されているより一般的なケースでは、「Merged dominating references」ビューを使用すると、どの参照が現在のオブジェクトセットをヒープ上に保持している原因であるかを突き止めるのに役立ちます。

ドミネーティング参照ツリー内の各ノードは、その参照を排除した場合に現在のオブジェクトセット内のいくつのオブジェクトがガーベッジコレクションの対象となるかを示します。複数のガーベッジコレクタールートから参照されているオブジェクトにはドミネーティングな被参照が存在しない場合があるため、このビューはオブジェクトの一部に対してしか役立たないか、空であることさえあります。その場合は、「Merged incoming references」ビューを使用して、ガーベッジコレクタールートを1つずつ排除していく必要があります。