実際起きたOutOfMemoryErrorの対応と、調べてみたことについてメモします。メモリの話のほうが多くなってしまいました。
0.OutOfMemoryError発生
java.lang.OutOfMemoryError: Java heap space
サーバがダウンしてからログを確認すると、上記がtomcatのcatalina.outに出ていました。 明らかにOOMEです。ヒントはJavaヒープ領域。それだけ。
そもそもOutOfMemoryErrorとは
を調べるために、JVMのメモリの管理の仕方を調べてみます。
JVMはメモリ空間をどのように使用しているか
JVMはOSからもらったメモリ空間を、目的別に分けて使用する頭のいい子です。 確保するメモリはStackとHeapに大別されます。
Stack
割当単位 | スレッドごとに1つ |
管理するもの |
ローカル変数 |
役割 |
Permanent領域に格納されているクラス情報からメソッド内容に読み込み、スタック領域にメソッドなどの処理を実行するための展開場所 |
GCの対象 | ×(処理がおわると自動的に保存・解放される) |
領域あふれると | StackOverflowError |
Heap
割当単位 | JVMごとに1つ |
管理するもの |
インスタンスそのもの |
役割 |
なんでもできるようデータの確保先を広く取っておくための場所。データの生存期間を自力で決定できる |
GCの対象 | ○ |
領域あふれると | OutOfMemoryError |
(http://promamo.com/?p=2828さまの図より引用)
ちなみにHeapは英語では「(ざっくばらんに山積みになっている)山のこと」を意味しますが、ここでのHeapはその意味と関係がないようです。それでもなぜHeapをHeapというのかについては議論があるようで、こちらも見てみるとおもしろいです。 文献によると「原始的なキューのことを1975年頃に”Heap”という名前で使い始めたらしい」ということだそうですね。しかし意味合いとしては似ている「(整理された山積みの)山」を指す”Pile”でもよかったんじゃないかとか、Heapはとりあえずがっつり領域を確保してしまうからそういうイメージなんじゃないかとか、いろいろ憶測があるようです。
Heap領域をもう少し詳しく
今回のOOMEはJava heap spaceということだったので、もう少し詳しくHeapを見てみます。 Heap領域はさらにいくつかの領域に分けられています。
- Java Heap(JVM固有領域) ... Newしたオブジェクトを管理する場所
- ここはさらにEden,Survivor,Tenured領域に分かれており、オブジェクトの使用頻度によって展開される領域が変わっていく。
- Permanent Heap(JVM固有領域)...クラスをロードする際に確保する場所
- C Heap(OS固有領域)...JVM自身が使用する領域
OutOfMemoryが出たときのアプローチ
このエラーはJavaのどこかのHeap領域でメモリが不足したということを意味します。 原因の切り分けのため、どこの領域(Java Heap、Permanent Heap、C Heap)でOutOfMemoryErrorになっているかを確認します。 今回のエラーは上述のように、「Java heap space」 と文字通りJava Heap領域のメモリが不足したために起こったようです。
1.Heap dumpを取ってみる
今までのログだけではまだ詳細がわからないので、Heap領域をそのまま覗いてみる Heap dumpを取ってみます。 JVMの起動オプションに、以下を指定して再起動。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat7/
文字通り、OutOfMermoryErrorが発生したときにHeap Dumpを取得する設定です。 ダンプのパスも指定しておきます。
2.OutOfMemoryError再来
さてOutOfMemoryErrorが再来すると、以下のようなログが指定したパスに出力されます。
-rw------- 1 tomcat tomcat 1712263784 Jun 13 10:34 java_pid6333.hprof
拡張子のhprofは(Heap Profiling)の略なのでしょうね。。 これはバイナリデータなので、調べるためにツールを使ってみます。
3.Heap dump調査
今回はMATのスタンドアロン版を利用しました。自分のマシンに合うインストーラをダウンロードし、hprofファイルを食わせてみます。
これだけで現在どこにメモリが使われているかがグラフ化されて出てくるというすばらしさ。 ここでメモリ使用率が多い部分のStackトレースが追えるので、該当するメソッドの部分での呼び出し方に問題がないかを確認してみました。
4.ソースコード直す
今回は1つのオブジェクトをDBから取得する際、それを参照するオブジェクトが多くなってきたためにそれらの分のメモリも確保しなければならなかったことが原因の1つと考えられました。この部分を修正し、運用環境に適応しました。
5.おしまい
今後も少しずつ整理してきたいと思います。
参考サイト
http://gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.htm
http://www.atmarkit.co.jp/ait/articles/1003/11/news091.html
http://itdoc.hitachi.co.jp/manuals/link/cosmi_v0950/03Y0440D/EY040139.HTM