こんにちは、サイオステクノロジー技術部 武井です。今回は、Javaアプリケーションのプロファイリングについてお話したいと思います。
プロファイリングとは?
Javaでアプリケーションを開発するときのあるあるとして、長時間動かしたら、もしくは一度に大量の負荷をかけたら、OutOfMemoryなんてことよくないでしょうか?私は今まで何度もOOMに枕を涙で濡らしました。
JavaはGC(Garbage Collection)という機能によって、使われなくなった不要なインスタンスを、独自のロジックでメモリからお掃除してくれます。自分でクローズする必要はありません。
しかしながら、自分の預かり知らぬところで、GCが行われるがゆえに、いざ、メモリーリークのあるようなプログラムを作ってしまったときに、その特定が困難を極めることがあります。
たいていはファイルアップロードしたりするときにバカでかいオブジェクトを突っ込んだとか、Staticな変数になにかをたくさん入れてしまったとか、ヒープの容量が足りなかったとかなのですが、そのどれにも該当せず、原因が把握できないときがあります。例えば、外部のライブラリが知らぬ間に悪さをしていたりとか、設定間違えてDBのコネクションプールをものすごい数してしまったりとか(T_T)
そんなときに役に立つのは、プロファイリングツールです。プロファイリングツールとは、その時のヒープの状態を取得し、どのインスタンスがどの程度作成されていて、全体のヒープのどれくらいを占有しているかを把握できるツールです。
早速使ってみましょう
まずは使ってみることが何よりです。今回はEclipse Memory Analyzerを使ってみました。以下のリンクからダウンロードが可能です。
https://www.eclipse.org/mat/downloads.php
あからさまにメモリリークを起こす以下のプログラムを作って動かしてみます。StaticなListに大量にhogeしています。せいぜい100,000インスタンスくらいですので、ドボンはしないと思いますが、怪しい結果にはなりそうですね。Thread.sleepしているのは、プロセスIDを取る時間を稼ぐためです。
public class MemoryLeak { private static List<String> list = new ArrayList<String>(); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 100000; i++) { list.add("hoge"); } System.out.println("done"); Thread.sleep(60000); } }
上記のプログラムが動いているときにプロセスIDを取得します。
# ps ax | grep java2で取得したプロセスIDをもとに、ヒープのダンプを取得します。
$ jmap -dump:format=b,file=heapdump.hprof [プロセスID]Eclipse Memory Analyzerを起動して、「File」→「Open Heap Dump」の順にクリックして、先程取得したダンプファイルを選択します。
「Leak Suspects Report」をチェックして「Finish」をクリックします。
以下のような円グラフが表示されます。(a)と(b)がProblem Suspectと表示されています。これが怪しいということになります。ヒープのトータルである921.9KBの半分近くを占めているわけですから。
ちょっと下にスクロールして、犯人であろう2つのオブジェクトのうちの1つの詳細を見てみることにします。ほら、なんかcom.sios.MemoryLeakとか書いているあたりで、なんか怪しそうな雰囲気醸し出していませんでしょうか?期待に胸を膨らませて、下図の「Details >>」をクリックします。
以下のような画面が表示されます。「Shallow Heap」はそのインスタンス単独のメモリのサイズ、「Retained Heap」はそのインスタンス及びそのインスタンスが持つ他のインスタンスも含めたメモリのサイズです。とりあえずでかいのが怪しいです。(¬_¬。) あやしい (;¬¬)アヤシイ・・・
listを右クリックして「List objects」→「with outgoing references」の順にクリックします。
キタ━━━━(゚∀゚)━━━━!!これを見る限り、com.sios.MemoryLeakっていうクラスにあるArrayList型のlist って言う変数が怪しそうです。listってさっきのプログラムでわざとStaticなListにhogeを大量に突っ込んだアレです。valueにもhogeって書いてありますね(ちっちゃくて見にくいですけど)。
想定の結果と、実際の結果が一致したわけです。犯人はやっぱりStaticなlistにhogeを大量に突っ込んだからですね。
まとめ
今回のは非常に簡単な例でした。でも実際は、自分でメモリーリークを起こすようなプログラムを書いていないつもりでも、外部のライブラリが怪しかったりとかして、その原因の特定に困難を極めることがあります。そんなときのプロファイリングツールです。もうくせになりそうです。三度の飯よりプロファイル、どんと来いOutOfMemory。では、快適なプロファイルライフを!!No Profiling,No Life!!ずんちゃずんちゃ。