【JTS】Vol.004:プログラムのメモリリークを判定する方法
問題
プログラムを動かすと、徐々にメモリ使用量が増加していきます。プログラムが正しい動作をしているのか、それともメモリリークしているのかどうか判断がつきません。
Java | OS |
---|---|
Java SE 5.0以降 | any |
解説
1週間・1ヶ月と長時間実行するプログラムで発生した場合に、最も問題になるのは、メモリリークです。メモリリークが発生するプログラムを長時間実行させると、メモリを使い尽くしてしまいます。メモリを使い尽くしたプログラムは、次のようにOutOfMemoryErrorを発生して、強制終了してしまいます。
Exception java.lang.OutOfMemoryError: requested 124 bytes for AAA
実運用と同じような長時間実行する試験は頻繁に出来ないため、プログラムを短い時間動かすだけで、メモリリークかどうかを分析する必要があります。
対策
Javaプログラムのメモリリークは、2つの領域で発生する場合があります。
- Javaヒープ
- Cヒープ
今回は、問題がおきやすいJavaヒープでのメモリリークについて説明します。
最初に、Javaヒープにおけるメモリリーク発見のための分析方法をまとめると、次のようになります。
- メモリ使用量全体が増えているか?増えて続けているか?
- どのオブジェクトがメモリリークしているのか?
ここでは、最初に1.のメモリ使用量の増加ついての確認を行い、次に2.のメモリリークしているオブジェクトを特定する方法を説明します。
1. メモリ使用量全体が増えているか?増えて続けているか?
メモリ使用量の増加を確認するためには、
- (a) jstatを利用する方法と
- (b) Javaの起動オプションにGCDetailsを指定する方法、の2つがあります。
(a) jstatを利用する
jstatを利用することで、Javaプロセスのメモリ利用状況を調べることができます。jstatは次のように利用します。
$ jstat -gc
$ jps 3535 MemoryLeakTest 3649 Jps
にはデータの更新周期をミリ秒単位で指定します。通常は、1000ミリ秒を指定すれば十分です。
jstatの結果は次のようになります。
$ jstat -gc 3770 1000 S0C S1C S0U S1U EC EU OC OU PC PU 64.0 64.0 0.0 16.0 5952.0 2144.2 28608.0 162.4 16384.0 1775.3 64.0 64.0 0.0 16.0 5952.0 5476.5 28608.0 162.4 16384.0 1775.3 64.0 64.0 16.0 0.0 5952.0 1547.7 28608.0 162.4 16384.0 1775.3 128.0 128.0 64.0 0.0 5952.0 4644.8 28608.0 162.4 16384.0 1775.3 128.0 128.0 0.0 16.0 5952.0 476.2 28608.0 162.4 16384.0 1775.3 128.0 128.0 0.0 16.0 5952.0 2976.4 28608.0 162.4 16384.0 1775.3 128.0 128.0 16.0 0.0 5952.0 0.0 28608.0 162.4 16384.0 1775.3 128.0 128.0 48.0 0.0 5952.0 3095.8 28608.0 162.4 16384.0 1775.3 ※実際にはPUの項目以降も表示されますが、紙面の都合で省略しています。
大量の情報が表示されますが、注目するのはOUの項目です。この値が徐々に大きくなる場合には、メモリリークの発生の疑いがあります。
2. PrintClassHistogramでリークしているオブジェクトを特定する
jstatやGCログの結果からメモリリークの疑いがある場合には、より詳細な分析を行い、リークしているオブジェクトを特定する必要があります。Javaの起動オプションに"-XX:+PrintClassHistogram"を追加して、再度プログラムを実行してください。起動したJavaプロセスにSIGQUITシグナルを送信することで、Full GCが強制実行され、Javaのヒープ使用状況がコンソールに表示されます。
シグナルは、Unix/Linuxであればkillコマンドを使って送信します。
$ kill -3
Windowsでは、Javaプロセスを立ち上げたコマンドプロンプトで、Ctrl + Break を押してください。
シグナルを受信したJavaプロセスは、ヒープ内のオブジェクトごとに、インスタンス数と使用しているメモリ領域の合計を表示します。具体例は次のようになります。
num #instances #bytes class name
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
メモリリークの発生を確認するには、時間を置いて何回かシグナルを送り、その結果を比較します。Totalのインスタンス数やバイト数が増加している場合には、メモリリークの可能性があります。また、インスタンス数やバイト数が上昇しているオブジェクトが、メモリリークの原因となっている可能性が高くなります。