JVM 実行中に
java.lang.OutOfMemoryError: Java heap space
または
java.lang.OutOfMemoryError: GC overhead limit exceeded
が出力された場合の対応方法のひとつをまとめます。
「Java ヒープ・メモリの構造」や「Javaのヒープ・メモリ管理の仕組みについて」にまとめられているように、JVM のメモリ管理は以下のようになっています。
| Cヒープ | スレッドスタック | Javaヒープ |
概要に記載した Out Of Memory Error (OOME) はいずれも Java ヒープが不足した場合に発生します。単純にアプリケーションの処理上必要なサイズが満たせていない場合は Xms と Xmx で割り当てれば解決します。そうではなく、処理上不要なはずのオブジェクトへの参照がループ毎に削除されずに蓄積されていきメモリリークが発生している場合はアプリの修正が必要です。メモリリークしているかどうかの判断には jstat コマンドが利用できます。
Java ヒープは更に以下のように細分化されています。JVM の GC には二種類あります。
| -----------New---------- | ---------------Old--------------- | Permanent |
| Eden | From/To | To/From | | |
jps コマンドで JVM の PID を調査してから、以下のコマンドを実行します。
$ jstat -gcutil -h5 <pid> 1000
以下のような出力が 1000 ミリ秒ごとに得られます。
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 97.02 70.31 66.80 95.52 89.14 7 0.300 0 0.000 0.300
0.00 97.02 86.23 66.80 95.52 89.14 7 0.300 0 0.000 0.300
0.00 97.02 96.53 66.80 95.52 89.14 7 0.300 0 0.000 0.300
91.03 0.00 1.98 68.19 95.89 91.24 8 0.378 0 0.000 0.378
91.03 0.00 15.82 68.19 95.89 91.24 8 0.378 0 0.000 0.378
91.03 0.00 17.80 68.19 95.89 91.24 8 0.378 0 0.000 0.378
91.03 0.00 17.80 68.19 95.89 91.24 8 0.378 0 0.000 0.378
-gcutil
GC の統計情報を出力-h5
5 行ごとにヘッダーを出力S0/S1
Survivor (From/To または To/From) 利用率E
Eden 利用率O
Old 利用率M
, CCS
Permanent 利用率YGC
Scavenge GC (Young GC) が JVM 起動時から今までに発生した回数YGCT
Scavenge GC (Young GC) のために JVM 起動時から今までに要した秒数FGC
Full GC が JVM 起動時から今までに発生した回数FGCT
Full GC のために JVM 起動時から今までに要した秒数GCT
YGCT + FGCTjava.lang.OutOfMemoryError: Java heap space
は文字通り Java ヒープの不足で発生します。一方で java.lang.OutOfMemoryError: GC overhead limit exceeded
は、こちらのページまとめられているように以下の条件で発生します。潜在的に java.lang.OutOfMemoryError: Java heap space
が発生する状況であるのだから、GC して無駄にユーザーを待たせるよりもさっさと落ちてしまいましょう、といった感じのエラーです。
GC の状況をみて、徐々に使用率が上昇しているようであればメモリリークしている可能性があります。jmap コマンドでヒープダンプを取得して Eclipse スタンドアロン Memory Analyzer (MAT) で解析を行い、メモリを消費しているオブジェクトを特定しましょう。インストールして起動したら「Open a Heap Dump」→「Leak Suspects Report」→「Problem Suspect N」を開いて可能性のあるオブジェクトを特定し、使い終わったら null を代入するなどコードを修正します。
import java.util.Random
import scala.collection.mutable.Map
object Main {
def main(args: Array[String]) : Unit = {
val map = Map[Int, String]()
val r = new Random
while(true) {
map += (r.nextInt() -> "value")
Thread.sleep(1)
}
}
}