メモリリークの分析方法を説明します。
まず、リークしているクラス(オブジェクトの種類)を特定します。リークしているクラスが分かったら、ヒープダンプを採取して、オブジェクトの参照関係を調べ、リークの原因となっているオブジェクトを特定します。
リークしているクラスを特定する方法について説明します。
まず、Java監視機能の[ヒープ分析]タブを開いて[開始]ボタンを押し、分析を始めてください。
図A.14 Java監視機能による、メモリリーク調査(分析の開始)
分析が開始されると、次のようなメッセージが表示されます。
図A.15 Java監視機能による、メモリリーク調査(分析開始後)
[終了]ボタンを押すと、分析が終了します。開始から終了までの時間は、十分とってください。終了のタイミングを計るには、[メモリ]タブを開いて、Javaヒープ使用量の推移を確認します。
図A.16 Java監視機能による、メモリリーク調査(Javaヒープ使用量の確認)
この例では、Javaヒープの使用量が、上昇し続けていることが分かります。
Javaヒープの使用量が増加し続けることが確認できたら、[ヒープ分析]の画面で[終了]ボタンを押します。
図A.17 Java監視機能による、メモリリーク調査(分析の終了方法)
分析が終了すると、オブジェクトヒストグラムが表示されます。測定期間中の増加量(バイト数)が多い順に、クラスが表示されます。
図A.18 Java監視機能による、メモリリーク調査(分析結果の表示)
オブジェクトヒストグラムを見て、大量に増加しているクラスを特定します。
開始時刻 Mon Sep 27 16:40:13 JST 2010 終了時刻 Mon Sep 27 16:44:44 JST 2010 増減[バイト] 増減[個数] クラス名 142348960 136874 byte[] 12930528 13656 int[] 2189984 136874 com.fujitsu.demo.MemLeak$Blob 558968 0 java.lang.Object[] 105544 83 char[] 33064 135 <methodDataKlass> 2040 85 java.lang.String |
まず、アプリケーションで作成したクラスが増加しているかどうかを確認します。この例では、com.fujitsu.demo.MemLeak$Blobクラスのインスタンスが、13万個以上増加していることが分かります。つまり、このクラスのインスタンスが、メモリリークしている可能性が考えられます。
ポイント
この例では、byteやintの配列も、大量に増加しています。しかし、プリミティブ型の配列は、リークとは関係なく増加している可能性も考えられます。
そのため、まずはアプリケーションで作成したクラスの増加量に着目するのが、リーク調査を効率的に行うコツです。アプリケーションで作成したクラスの増加量が小さい場合は、プリミティブ型の配列や、コアライブラリのクラスの増加量に着目します。
ここまでの調査で、com.fujitsu.demo.MemLeak$Blobクラスのインスタンスがリークしている可能性が大きいことが分かりました。
次に、ヒープダンプを採取してオブジェクトの参照関係を調べ、リークの原因となっているオブジェクトを特定します。
増加しているクラスの種類は、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クラスの参照関係を調べます。
Java監視機能の[ヒープ分析]タブで[ダンプを取得する]ボタンを押して、ヒープダンプを採取してください。
図A.19 Java監視機能による、メモリリーク調査(ヒープダンプ採取)
ヒープダンプが出力されたファイル名は、ボタンの右側に表示されます。このファイルを、jhatで解析します。
ポイント
ヒープダンプの出力(ファイル)は、アプリケーションが動作しているマシンに生成されます。
ヒープダンプは、jmapコマンドを使って採取することもできます。その場合、「-dump」オプションに「live」サブオプションを付けて、生存オブジェクトだけを含むヒープダンプを採取してください。
例
jhatの起動例
以下は、ローカルマシンにコピーしたヒープダンプを、jhatを起動する例です。
> jhat fjvm-heapdump.5216.0927164520
インターネットブラウザでアクセスするURLの例
jhatを起動したマシンに、インターネットブラウザでアクセスします。アクセスするポート番号は、デフォルトでは7000番です。
http://localhost:7000/object/0x10553ce0
以下は、ローカルマシンで動作しているjhatに対して、Firefoxでアクセスしたときの画面の例です。
図A.20 jhatによる調査(jhatのトップ画面)
増加しているcom.fujitsu.demo.MemLeak$Blobクラスのリンクをクリックすると、そのクラスに関する情報が表示されます。
図A.21 jhatによる調査(増加量が多いクラスの情報)
次に、このクラスのインスタンスを表示するため、「Instances」の「Exclude subclasses」をクリックします。
図A.22 jhatによる調査(増加量が多いクラスのインスタンス一覧)
com.fujitsu.demo.MemLeak$Blobクラスのインスタンスの一覧が表示されました。一覧から、適当なインスタンスのリンクをクリックします。
図A.23 jhatによる調査(オブジェクトの参照元の調査)
インスタンスに関する情報が表示されました。
com.fujitsu.demo.MemLeak$Blobオブジェクトを参照しているオブジェクトを特定するため、「References to this object」に表示されているリンクをクリックします。
図A.24 jhatによる調査(オブジェクトの参照元の情報)
com.fujitsu.demo.MemLeak$Blobオブジェクトの参照元は、巨大な配列であることが分かりました。ブラウザのスクロールバーを下に移動して「References to this object」を見ると、更に参照元があることが分かります。
図A.25 jhatによる調査(配列の参照元の調査)
このようにして「References to this object」のリンクを辿っていくと、com.fujitsu.demo.MemLeakのstatic変数blobsから参照していることが分かりました。
図A.26 jhatによる調査(オブジェクトの参照元の調査)
com.fujitsu.demo.MemLeakクラスのリンクをクリックして、クラスの情報を表示してみると、確かにstatic変数blobsが存在しました。この変数は、java.util.Listインタフェースを実装したクラスのインスタンスを指しています。
図A.27 jhatによる調査(メモリリークの原因となっているstatic変数の例)
つまり、このblobsにcom.fujitsu.demo.MemLeak$Blobを追加したあと、不要になった時点でオブジェクトをリストから削除しているかどうか、プログラムを確認する必要があります。
Java VisualVMを使った調査
メモリリークは、Java VisualVMを使って調べることもできます。詳しくは、JDKドキュメントを参照してください。