OutOfMemoryに泣く

こんにちは、サイオステクノロジー技術部 武井です。今回は、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 java

2で取得したプロセスIDをもとに、ヒープのダンプを取得します。

$ jmap -dump:format=b,file=heapdump.hprof [プロセスID]

Eclipse Memory Analyzerを起動して、「File」→「Open Heap Dump」の順にクリックして、先程取得したダンプファイルを選択します。

Screen Shot 2019-05-21 at 22.45.41

 

「Leak Suspects Report」をチェックして「Finish」をクリックします。

Screen Shot 2019-05-21 at 22.46.04

 

以下のような円グラフが表示されます。(a)と(b)がProblem Suspectと表示されています。これが怪しいということになります。ヒープのトータルである921.9KBの半分近くを占めているわけですから。

Screen Shot 2019-05-21 at 22.55.24

 

ちょっと下にスクロールして、犯人であろう2つのオブジェクトのうちの1つの詳細を見てみることにします。ほら、なんかcom.sios.MemoryLeakとか書いているあたりで、なんか怪しそうな雰囲気醸し出していませんでしょうか?期待に胸を膨らませて、下図の「Details >>」をクリックします。

Screen Shot 2019-05-21 at 22.55.34

 

以下のような画面が表示されます。「Shallow Heap」はそのインスタンス単独のメモリのサイズ、「Retained Heap」はそのインスタンス及びそのインスタンスが持つ他のインスタンスも含めたメモリのサイズです。とりあえずでかいのが怪しいです。(¬_¬。) あやしい (;¬¬)アヤシイ・・・

Screen Shot 2019-05-21 at 22.55.42

 

listを右クリックして「List objects」→「with outgoing references」の順にクリックします。

Screen Shot 2019-05-21 at 22.56.01

 

キタ━━━━(゚∀゚)━━━━!!これを見る限り、com.sios.MemoryLeakっていうクラスにあるArrayList型のlist って言う変数が怪しそうです。listってさっきのプログラムでわざとStaticなListにhogeを大量に突っ込んだアレです。valueにもhogeって書いてありますね(ちっちゃくて見にくいですけど)。

Screen Shot 2019-05-21 at 22.57.52

 

想定の結果と、実際の結果が一致したわけです。犯人はやっぱりStaticなlistにhogeを大量に突っ込んだからですね。

まとめ

今回のは非常に簡単な例でした。でも実際は、自分でメモリーリークを起こすようなプログラムを書いていないつもりでも、外部のライブラリが怪しかったりとかして、その原因の特定に困難を極めることがあります。そんなときのプロファイリングツールです。もうくせになりそうです。三度の飯よりプロファイル、どんと来いOutOfMemory。では、快適なプロファイルライフを!!No Profiling,No Life!!ずんちゃずんちゃ。

figure_dance

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

3人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です