Java言語コード内のリークの診断は難しい可能性があります。 通常、アプリケーションの非常に詳しい知識が必要です。 さらに、プロセスが反復し、冗長なことがよくあります。 この項では、Java言語コード内のメモリー・リークを診断するために使用できるツールについて説明します。
ノート: この項で説明したツールに加えて、数多くのサードパーティ・メモリー・デバッガ・ツールが利用可能です。 メモリー・デバッグ機能を備えた商用ツールの例として、Eclipse Memory Analyzer Tool (MAT)とYourKit (www.yourkit.com)の2つがあります。 他にも数多くありますが、推奨される特定の製品はありません。 |
Java言語コードのリークの診断に使用される2つのユーティリティを次に示します。
NetBeansプロファイラ: NetBeansプロファイラは、メモリー・リークの場所をすばやく見つけることができます。 商用のメモリー・リーク・デバッグ・ツールは、大規模なアプリケーション内のリークを特定するのに長い時間がかかる場合があります。 しかし、NetBeans Profilerは、このようなオブジェクトが一般的に示すメモリーの割り当てと再利用のパターンを使用します。 このプロセスには、メモリー再利用の欠落も含まれます。 このプロファイラは、これらのオブジェクトがどこで割り当てられたかをチェックできます(リークの根本原因を特定するには、通常はこれで十分です)。
詳細は、「NetBeansプロファイラ」を参照してください。
jhatユーティリティ: jhat
ユーティリティは、予期しないオブジェクトの保持(またはメモリー・リーク)をデバッグする場合に役立ちます。 オブジェクト・ダンプを参照し、ヒープ内の到達可能なすべてのオブジェクトを表示し、どの参照がオブジェクトを保持しているかを理解するための方法を提供します。
jhat
を使用するには、実行中のアプリケーションのヒープ・ダンプを1つ以上取得する必要があり、ダンプはバイナリ形式である必要があります。 作成されたダンプ・ファイルは、jhat
への入力として使用できます。 jhatユーティリティを参照してください。
次の項では、Java言語コードでのリークを診断するためのその他の方法について説明します。
ヒープ・ダンプは、ヒープ・メモリーの割当てに関する詳細情報を提供します。 ヒープ・ダンプを作成するには、いくつかの方法があります。
HPROFは、アプリケーションとともに起動された場合にヒープ・ダンプを作成できます。 例3-1に、コマンドの使用方法を示します。
ノート: JVMが埋め込まれているか、追加オプションを指定できるコマンド行ランチャを使用して起動されていない場合は、JAVA_TOOLS_OPTIONS 環境変数を使用して-agentlib オプションをコマンド行に自動的に追加できる可能性があります。 この環境変数の詳細は、「JAVA_TOOL_OPTIONS 環境変数」を参照してください。 |
アプリケーションがHPROFとともに実行されていれば、アプリケーション・コンソールで[Ctrl]+[\]または[Ctrl]+[Break](プラットフォームによる)を押すことで、ヒープ・ダンプを作成できます。 Oracle SolarisおよびLinuxオペレーティング・システムでの代替アプローチには、kill -QUIT
pidコマンドでQUITシグナルを送信する方法があります。 このシグナルが受信されると、指定されたPIDを持つプロセスのヒープ・ダンプが作成されます。前述の例では、snapshot.hprof
ファイルが作成されます。
ヒープ・ダンプ・ファイルには、すべてのプリミティブ・データとスタック・トレースが含まれています。
1つのダンプ・ファイルに複数のヒープ・ダンプを含めることができます。 [Ctrl]+[\]または[Ctrl]+[Break]を何度も押すと、後続のダンプがファイルの末尾に追加されます。 jhat
ユーティリティでは、#n
構文(n
はダンプの番号)を使用してダンプを区別します。 HPROFを参照してください。
jmap
ユーティリティを使用すると、実行中のプロセスのヒープ・ダンプを作成できます。
診断機能を強化し、パフォーマンスのオーバーヘッドを削減するには、jmap
ユーティリティのかわりに最新のユーティリティjcmd
の使用をお薦めします。 jcmdユーティリティの便利なコマンドを参照してください。 例3-2のコマンドでは、jcmd
を使用して実行中のプロセスのヒープ・ダンプを作成し、例3-3のjmap
コマンドと同じような結果が得られます。
JVMの起動方法に関係なく、jmap
ツールはヒープ・ダンプのスナップショットをsnapshot.jmap
という名前のファイル(前述の例の場合)に生成します。 jmap
の出力ファイルにはすべてのプリミティブ・データが含まれるはずですが、オブジェクトが作成された場所を示すスタック・トレースは含まれません。 jmapユーティリティを参照してください。
ヒープ・ダンプを作成するもう1つの方法は、JConsoleユーティリティを使用することです。 「MBean」タブで「HotSpotDiagnostic」MBeanを選択すると、「操作」が表示されるので、「dumpHeap」操作を選択します。
アプリケーションの実行中に-XX:+HeapDumpOnOutOfMemoryError
コマンド行オプションを指定すると、OutOfMemoryError
例外がスローされたときに、JVMによってヒープ・ダンプが生成されます。
ヒープ・ヒストグラムを調べることで、メモリー・リークをすばやく絞り込んでみることができます。 これは複数の方法で取得できます。
-XX:+PrintClassHistogram
コマンド行オプションを指定してJavaプロセスを起動すると、Ctrl+Breakハンドラによってヒープ・ヒストグラムが生成されます。
jmap
ユーティリティを使用して、実行中のプロセスからヒープ・ヒストグラムを取得できます。
診断機能を強化し、パフォーマンスのオーバーヘッドを削減するには、jmap
ユーティリティのかわりに最新のユーティリティjcmd
の使用をお薦めします。 jcmdユーティリティの便利なコマンドを参照してください。例3-4のコマンドでは、jcmd
を使用して実行中のプロセスのヒープ・ヒストグラムを作成し、jmap
コマンドと同じような結果が得られます。
例3-4 jcmdによるヒープ・ヒストグラムの作成
jcmd <process id/main class> GC.class_histogram filename=Myheaphistogram
jmap -histo pid
この出力には、ヒープ内の各クラス型の合計サイズとインスタンス数が表示されます。 ヒストグラムを連続して(たとえば、2分ごとに)取得すると、詳細な分析につなげることができる傾向を観察できる場合があります。
例3-5に示すように、jmap
ユーティリティを使用してコア・ファイルからヒープ・ヒストグラムを取得できます。
たとえば、アプリケーションの実行中に-XX:+HeapDumpOnOutOfMemoryError
コマンド行オプションを指定すると、OutOfMemoryError
例外がスローされたときに、JVMによってヒープ・ダンプが生成されます。 その後、例3-6に示すように、コア・ファイルでjmap
を実行してヒストグラムを取得できます。
例3-6 コア・ファイルでのjmapの実行
$ jmap -histo \ /java/re/javase/6/latest/binaries/solaris-sparc/bin/java core.27421
Attaching to core core.27421 from executable
/java/re/javase/6/latest/binaries/solaris-sparc/bin/java, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 1.6.0-beta-b63
Iterating over heap. This may take a while...
Heap traversal took 8.902 seconds.
Object Histogram:
Size Count Class description
-------------------------------------------------------
86683872 3611828 java.lang.String
20979136 204 java.lang.Object[]
403728 4225 * ConstMethodKlass
306608 4225 * MethodKlass
220032 6094 * SymbolKlass
152960 294 * ConstantPoolKlass
108512 277 * ConstantPoolCacheKlass
104928 294 * InstanceKlassKlass
68024 362 byte[]
65600 559 char[]
31592 359 java.lang.Class
27176 462 java.lang.Object[]
25384 423 short[]
17192 307 int[]
:
例3-6は、java.lang.String
オブジェクトの数(ヒープ内の3,611,828個のインスタンス)によってOutOfMemoryError
例外が生じたことを示しています。 文字列がどこで割り当てられたかは、詳細な分析を行わないとはっきりしません。 それでもなおこの情報は有用です。 HPROF
やjhat
などのツールを使用して引き続き調査を行うことで、文字列がどこで割り当てられたかや、どの参照によってそれらが保持され、ガベージ・コレクトされなくなっているかを特定できます。
OutOfMemoryError例外が「Java heap space」という詳細メッセージとともにスローされた場合、その原因はファイナライザの過度の使用によるものである可能性があります。 これを診断するために、ファイナライズを保留しているオブジェクトの数をモニターするためのオプションが複数用意されています。
JConsole管理ツールを使用して、ファイナライズを保留中のオブジェクト数をモニターできます。 このツールでは、「サマリー」タブ・ペインのメモリー統計にファイナライズ保留中の数が報告されます。 この数は概算ですが、アプリケーションを特徴付け、アプリケーションがファイナライズに大きく依存しているかどうかを理解するために使用できます。
Oracle SolarisおよびLinuxオペレーティング・システムでは、jmap
ユーティリティを-finalizerinfo
オプションとともに使用することで、ファイナライズを待機しているオブジェクトに関する情報を出力できます。
アプリケーションで、java.lang.management.MemoryMXBean
クラスのgetObjectPendingFinalizationCount
メソッドを使用して、ファイナライズを保留しているオブジェクトのおよその数を報告できます。 APIドキュメントへのリンクおよびコード例は、「カスタム診断ツール」に記載されています。 コード例を拡張して、ファイナライズ保留中の数の報告を簡単に追加できます。
ファイナライズの検出および移行の詳細は、「Java Platform, Standard Edition HotSpot仮想マシン・ガベージ・コレクション・チューニング・ガイド」の「ファイナライズおよび弱い参照、ソフト参照およびファントム参照」を参照してください。