目次
JVM 実行中に
java.lang.OutOfMemoryError: Java heap space
または
java.lang.OutOfMemoryError: GC overhead limit exceeded
が出力された場合の対応方法のひとつをまとめます。
jstat コマンドで JVM のメモリ使用状況を把握
「Java ヒープ・メモリの構造」や「Javaのヒープ・メモリ管理の仕組みについて」にまとめられているように、JVM のメモリ管理は以下のようになっています。
| Cヒープ | スレッドスタック | Javaヒープ |
- Cヒープ → JVM が OS のネイティブライブラリを実行する際に使用
- スレッドスタック → スタック領域
- Javaヒープ → 変数やオブジェクトなどを格納する領域 (Xms と Xmx でサイズを指定します)
概要に記載した Out Of Memory Error (OOME) はいずれも Java ヒープが不足した場合に発生します。単純にアプリケーションの処理上必要なサイズが満たせていない場合は Xms と Xmx で割り当てれば解決します。そうではなく、処理上不要なはずのオブジェクトへの参照がループ毎に削除されずに蓄積されていきメモリリークが発生している場合はアプリの修正が必要です。メモリリークしているかどうかの判断には jstat コマンドが利用できます。
ガベージコレクション (GC) について
Java ヒープは更に以下のように細分化されています。JVM の GC には二種類あります。
| -----------New---------- | ---------------Old--------------- | Permanent |
| Eden | From/To | To/From | | |
- オブジェクトが生成されると Eden に格納される
- Eden が満杯になると Scavenge GC が実行されて参照されていれば To に移される
- From と To のラベルが入れ替わる
- Eden が満杯になると Scavenge GC が実行されて Eden と From の参照されていないオブジェクトは To に移される。これを繰り返す
- From/To と To/From を行ったり来たりしながら所定の回数 (MaxTenuringThreshold) を越えても参照されているオブジェクトは Old に移される
- Old 領域も満杯になってくると Full GC が実行される
補足
- From/To と To/From はまとめて Survivor ともよばれる
- Old は Tenured (身分保証のある) ともよばれる
- New + Old + Permanent の初期値は Xms で指定できる (Xmx と同じ値にしておくことが推奨される)
- New + Old + Permanent の最大値は Xmx で指定できる
- New のサイズは Xmn で指定できる (Xmx の 25% から 50% の範囲で設定。短命なオブジェクトが多い場合は 50% にして YGC の回数を減らし Old への移動を避けて結果的に FGC 回数も減らす)
jstat コマンドで GC の様子をみる
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 + FGCT
GC overhead limit exceeded とは何か
java.lang.OutOfMemoryError: Java heap space
は文字通り Java ヒープの不足で発生します。一方で java.lang.OutOfMemoryError: GC overhead limit exceeded
は、こちらのページまとめられているように以下の条件で発生します。潜在的に java.lang.OutOfMemoryError: Java heap space
が発生する状況であるのだから、GC して無駄にユーザーを待たせるよりもさっさと落ちてしまいましょう、といった感じのエラーです。
- ほぼすべての CPU 時間を GC のために使用するようになってしまった
- GC を実行したにも関わらず Java ヒープの空きが Xmx の 2% 以下しかない
ヒープダンプして解析
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)
}
}
}
ヒープダンプの取得
pid の調査
jps -m
ダンプを取得
jmap -dump:format=b,file=heapdump.map <pid>
反応がない場合は強制出力 -F
jmap -F -dump:format=b,file=heapdump.map <pid>
解析
円グラフで大半を占めているのは (a) です。
(a) の Thread Stack をみると Main.scala の 12 行目と表示されています。
関連記事
- Spring Security フォームログインのサンプルコードSpring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
- Java配列の宣言方法 (C/C++との違い)Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
- PlantUML による UML 図の描き方PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
- Akka HTTP サンプルコード (Scala)Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
- Kestrel の使用例Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...