メモリリークの分析方法を説明します。
まず、リークしているクラス(オブジェクトの種類)を特定します。リークしているクラスが分かったら、ヒープダンプを採取して、オブジェクトの参照関係を調べ、リークの原因となっているオブジェクトを特定します。
増加しているクラスの種類は、jmapコマンドを使って調べます。jmapコマンドを次のように実行します。
実際には、標準出力をリダイレクトするなどして、結果をファイルなどに保存してください。
jmap -histo:live <プロセスID> |
このとき、「-histo」オプションに「live」サブオプションを付けてください。「:live」を付けると、ヒストグラムを採取する前にGCを実行します。つまり、GC直後の生存オブジェクトだけを含むヒストグラムを得られます。これは、メモリリークの調査において重要な点です。
jmapコマンドを複数回実行したあとで、採取したヒストグラムを比較してください。オブジェクトが増加しているクラスがあれば、メモリリークしている可能性が考えられます。
例えば、「jmapで採取したヒストグラムの例(1)」でそれぞれ15,923個、15,342個だったbyte配列とcom.fujitsu.demo.MemLeak$Blobが、「jmapで採取したヒストグラムの例(2)」の時点では184,406個と183,823個に増加しています。
jmapで採取したヒストグラムの例(1)
num #instances #bytes class name |
このような場合は、これらのオブジェクトについて、メモリリークが発生しているかどうか調べていきます。
jmapで採取したヒストグラムの例(2)
num #instances #bytes class name |
ヒープダンプを採取して、com.fujitsu.demo.MemLeak$Blobクラスの参照関係を調べます。
jmapコマンドで、「-dump」オプションに「live」サブオプションを付けて、生存オブジェクトだけを含むヒープダンプを採取してください。
ヒープダンプが出力されたファイルを、jhatで解析します。
ポイント
ヒープダンプの出力(ファイル)は、アプリケーションが動作しているマシンに生成されます。
例
jhatの起動例
以下は、ローカルマシンにコピーしたヒープダンプを、jhatを起動する例です。
> jhat heapdump.5216.0927164520
インターネットブラウザでアクセスするURLの例
jhatを起動したマシンに、インターネットブラウザでアクセスします。アクセスするポート番号は、デフォルトでは7000番です。
http://localhost:7000/object/0x10553ce0
以下は、ローカルマシンで動作しているjhatに対して、Firefoxでアクセスしたときの画面の例です。
図A.4 jhatによる調査(jhatのトップ画面)
増加しているcom.fujitsu.demo.MemLeak$Blobクラスのリンクをクリックすると、そのクラスに関する情報が表示されます。
図A.5 jhatによる調査(増加量が多いクラスの情報)
次に、このクラスのインスタンスを表示するため、「Instances」の「Exclude subclasses」をクリックします。
図A.6 jhatによる調査(増加量が多いクラスのインスタンス一覧)
com.fujitsu.demo.MemLeak$Blobクラスのインスタンスの一覧が表示されました。一覧から、適当なインスタンスのリンクをクリックします。
図A.7 jhatによる調査(オブジェクトの参照元の調査)
インスタンスに関する情報が表示されました。
com.fujitsu.demo.MemLeak$Blobオブジェクトを参照しているオブジェクトを特定するため、「References to this object」に表示されているリンクをクリックします。
図A.8 jhatによる調査(オブジェクトの参照元の情報)
com.fujitsu.demo.MemLeak$Blobオブジェクトの参照元は、巨大な配列であることが分かりました。ブラウザのスクロールバーを下に移動して「References to this object」を見ると、更に参照元があることが分かります。
図A.9 jhatによる調査(配列の参照元の調査)
このようにして「References to this object」のリンクを辿っていくと、com.fujitsu.demo.MemLeakのstatic変数blobsから参照していることが分かりました。
図A.10 jhatによる調査(オブジェクトの参照元の調査)
com.fujitsu.demo.MemLeakクラスのリンクをクリックして、クラスの情報を表示してみると、確かにstatic変数blobsが存在しました。この変数は、java.util.Listインタフェースを実装したクラスのインスタンスを指しています。
図A.11 jhatによる調査(メモリリークの原因となっているstatic変数の例)
つまり、このblobsにcom.fujitsu.demo.MemLeak$Blobを追加したあと、不要になった時点でオブジェクトをリストから削除しているかどうか、プログラムを確認する必要があります。
Java VisualVMを使った調査
メモリリークは、Java VisualVMを使って調べることもできます。
Java VisualVMの[監視]タブを開き[ヒープダンプ]ボタンを押して、ヒープダンプを採取してください。
他にも、Java VisualVMのメニューバーの[アプリケーション(A)]→[ヒープダンプ(H)]、または、アプリケーションのツリー上で対象のプロセスを右クリックして[ヒープダンプ(H)]を押すことでも、同様にヒープダンプを採取できます。
詳しくは、JDKドキュメントを参照してください。