モーダルを閉じる工作HardwareHub ロゴ画像

工作HardwareHubは、ロボット工作や電子工作に関する情報やモノが行き交うコミュニティサイトです。さらに詳しく

利用規約プライバシーポリシー に同意したうえでログインしてください。

目次目次を開く/閉じる

JVM メモリリーク時の対応

モーダルを閉じる

ステッカーを選択してください

お支払い手続きへ
モーダルを閉じる

お支払い内容をご確認ください

購入商品
」ステッカーの表示権
メッセージ
料金
(税込)
決済方法
GooglePayマーク
決済プラットフォーム
確認事項

利用規約をご確認のうえお支払いください

※カード情報はGoogleアカウント内に保存されます。本サイトやStripeには保存されません

※記事の執筆者は購入者のユーザー名を知ることができます

※購入後のキャンセルはできません

作成日作成日
2016/02/08
最終更新最終更新
2021/10/07
記事区分記事区分
一般公開

目次

    ニューラルネットワークを用いたAI研究を行っています。

    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 |                                   |           |
    
    1. オブジェクトが生成されると Eden に格納される
    2. Eden が満杯になると Scavenge GC が実行されて参照されていれば To に移される
    3. From と To のラベルが入れ替わる
    4. Eden が満杯になると Scavenge GC が実行されて Eden と From の参照されていないオブジェクトは To に移される。これを繰り返す
    5. From/To と To/From を行ったり来たりしながら所定の回数 (MaxTenuringThreshold) を越えても参照されているオブジェクトは Old に移される
    6. 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 行目と表示されています。

    Likeボタン(off)0
    詳細設定を開く/閉じる
    アカウント プロフィール画像

    ニューラルネットワークを用いたAI研究を行っています。

    記事の執筆者にステッカーを贈る

    有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。

    >>さらに詳しくステッカーを贈る
    ステッカーを贈る コンセプト画像

    Feedbacks

    Feedbacks コンセプト画像

      ログインするとコメントを投稿できます。

      ログインする

      関連記事

      • Spring Security フォームログインのサンプルコード
        Spring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
        えびちゃんえびちゃん12/4/2019に更新
        いいねアイコン画像0
      • 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]; となる。 初期化
        てんとうむしてんとうむし5/13/2018に更新
        いいねアイコン画像0
      • PlantUML による UML 図の描き方
        サムネイル画像-c788fffde5
        PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
        kentakenta1/21/2020に更新
        いいねアイコン画像0
      • Akka HTTP サンプルコード (Scala)
        サムネイル画像-a98142497c
        Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
        雄太雄太10/7/2021に更新
        いいねアイコン画像0
      • Kestrel の使用例
        Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...
        したくんしたくん10/12/2017に更新
        いいねアイコン画像0