My Oracle Support Banner

OutOfMemoryError とメモリリークに関する調査のメソドロジー (Doc ID 1544030.1)

Last updated on JUNE 01, 2020

適用範囲:

Oracle WebLogic Server - バージョン 8.1 以降
この文書の内容はすべてのプラットフォームに適用されます。
本文書は英語で提供されている Document 1054263.1 を翻訳したものです。

目的

本文書で取り扱う範囲

本文書では Java ヒープでの "java.lang.OutOfMemoryError" を調査するためのメソドロジー(方法論)をサンプルを使って説明します。ネイティブヒープでの OutOfMemoryError については取り扱いません。本文書で扱うのはメソドロジーのみであり、実際の調査で使用できるツールは問題が発生した環境によって異なります。しかし、メソドロジーはツールに依存せず、JVM や プラットフォーム/OSが異なっても利用することができます。

イントロダクション

  1. Java ヒープにおける java.lang.OutOfMemoryError の発生は何を意味するでしょうか?

    アプリケーションが OutOfMemoryError を出力するのは、アプリケーションを動作させる Java 仮想マシンのヒープ(メモリ)が枯渇した時です。
     
  2. OutOfMemory(OOM)エラーやメモリの枯渇を発生させうる要因はどのようなものでしょうか?

    高レベルで考えると、Java ヒープの OutOfMemory は二つに分類することができます。
              a. 過負荷による純粋なメモリの枯渇(割当て可能なヒープ以上のリクエストをアプリケーションが行った)
              b. Java ヒープにおけるメモリリークの発生
     
  3. Java ヒープにおけるメモリリークとは何でしょうか?

    定期的にガベージコレクション(GC) が発生しているにもかかわらず、Java ヒープの使用量が継続的に増加していくことです。

    低レベルで更に説明しましょう。

    プログラムがその後利用されないヒープチャンクへの参照を持ち続けていると、メモリを解放・再利用できなくなるため、メモリリークと見なされます。GC はプログラムが参照を保持し続けているとメモリを回収しようとしません。このようなリークによって、Java プログラムはメモリを枯渇させてしまう場合があります。Java のメモリリークのほとんどは、気付きにくいプログラミングの問題によって引き起こされます。単純な例を以下に示します。
    import java.io.IOException;
    import java.util.HashSet;
    import java.util.Random;
    import java.util.Vector;

    public class LeakExample {
    static Vector myVector = new Vector();
    static HashSet pendingRequests = new HashSet();

    public void slowlyLeakingVector(int iter, int count) {
      for (int i=0; i<iter; i++) {
        for (int n=0; n<count; n++) {
          myVector.add(Integer.toString(n+i));
        }
        for (int n=count-1; n>0; n--) {
        // Oops, it should be n>=0
        myVector.removeElementAt(n);
        }
      }
    }

    public void leakingRequestLog(int iter) {
      Random requestQueue = new Random();
      for (int i=0; i<iter; i++) {
        int newRequest = requestQueue.nextInt();
        pendingRequests.add(new Integer(newRequest));
        // processed request, but forgot to remove it
        // from pending requests
      }
    }

    public void noLeak(int size) {
      HashSet tmpStore = new HashSet();
      for (int i=0; i<size; ++i) {
        String leakingUnit = new String("Object: " + i);
        tmpStore.add(leakingUnit);
      }
      // Though highest memory allocation happens in this
      // function, but all these objects get garbage
      // collected at the end of this method, so no leak.
    }

    public static void main(String[] args) throws IOException {
      LeakExample javaLeaks = new LeakExample();
      for (int i=0; true; i++) {
        try { // sleep to slow down leaking process
          Thread.sleep(1000);
        } catch (InterruptedException e) { /* do nothing */ }
          System.out.println("Iteration: " + i);
          javaLeaks.slowlyLeakingVector(1000,10);
          javaLeaks.leakingRequestLog(5000);
          javaLeaks.noLeak(100000);
      }
    }
    }

    LeakExample クラスには slowlyLeakingVector、leakingRequestLog、noLeak という3つのメソッドがあります。slowlyLeakingVector は二つ目の for ループで条件の設定ミスをしているため、繰り返しの度に一つの String オブジェクトがリークすることになります。もしこれが String ではなく大きなサイズのデータベースレコードであれば、このリークは急激なメモリの枯渇を招き、アプリケーションの動作を止めてしまう可能性もあるでしょう。leakingRequestLog は処理が完了するまでリクエストをハッシュテーブルに格納するという、ありふれた処理を行っています。ただし、この例ではリクエストが完了した後にそれをハッシュテーブルから削除するという処理をプログラマーが忘れてしまっています。時間が経つにつれハッシュテーブルは多数のエントリを持つようになり、不要なエントリによるハッシュの衝突やヒープの圧迫を招くことになります。メモリリークは、しばしばこれらのようなミスによって引き起こされます。

    最もメモリを消費するポイントでリークが発生していると思い込んでしまうことがあります。しかし、それは必ずしも正しいとは限りません。例えば noLeak メソッドは沢山のメモリを使用しますが、それらは全てガベージコレクションで回収されます。一方で他の二つのメソッドはそれほど大きなメモリを消費するわけではありませんが、メモリリークを発生させます。

トラブルシューティングの手順

To view full details, sign in with your My Oracle Support account.

Don't have a My Oracle Support account? Click to get started!


本書の内容
目的
 本文書で取り扱う範囲
 イントロダクション
トラブルシューティングの手順
 OOM の原因を特定する
 メモリリークのデバッグ
参照情報

My Oracle Support provides customers with access to over a million knowledge articles and a vibrant support community of peers and Oracle experts.