web-dev-qa-db-ja.com

Java.lang.OutOfMemoryErrorをキャッチしますか?

ドキュメント for Java.lang.Errorのコメント:

エラーはThrowableのサブクラスであり、合理的なアプリケーションがキャッチしようとしてはならない重大な問題を示します

しかし、Java.lang.ErrorJava.lang.Throwableのサブクラスなので、このタイプのThrowableをキャッチできます。

この種の例外をキャッチするのが良い考えではない理由を理解しています。私が理解している限りでは、キャッチすることに決めた場合、キャッチハンドラはそれ自体でメモリを割り当てるべきではありません。それ以外の場合は、OutOfMemoryErrorが再びスローされます。

だから、私の質問は:

  1. Java.lang.OutOfMemoryErrorをキャッチするのが良い考えかもしれない現実のシナリオはありますか?
  2. Java.lang.OutOfMemoryErrorをキャッチすることにした場合、キャッチハンドラーがそれ自体でメモリを割り当てないようにするにはどうすればよいですか(ツールやベストプラクティス)。
93
Denis Bazhenov

ここでのほとんどの回答に同意します。

OutOfMemoryErrorをキャッチしたいシナリオがいくつかありますが、私の経験では(WindowsおよびSolaris JVMで)、OutOfMemoryErrorがJVMの致命的な問題になることはほとんどありません。

OutOfMemoryErrorをキャッチする正当な理由は1つだけです。それは、適切に終了し、リソースをきれいに解放し、できる限り失敗の理由をログに記録することです(可能な場合)。

一般的に、OutOfMemoryErrorは、ヒープの残りのリソースでは満足できないブロックメモリ割り当てが原因で発生します。

Errorがスローされると、ヒープには、失敗した割り当ての前と同じ量の割り当て済みオブジェクトが含まれ、クリーンアップに必要なメモリをさらに解放するために、実行時オブジェクトへの参照を削除します。これらの場合、続行することも可能かもしれませんが、JVMが修復可能な状態であることを100%確実に知ることはできないため、それは間違いなく悪い考えです。

OutOfMemoryErrorは、JVMのcatchブロックのメモリが不足しているという意味ではありません。

_private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
    MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    for (int i=1; i <= 100; i++) {
        try {
            byte[] bytes = new byte[MEGABYTE*500];
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
            long maxMemory = heapUsage.getMax() / MEGABYTE;
            long usedMemory = heapUsage.getUsed() / MEGABYTE;
            System.out.println(i+ " : Memory Use :" + usedMemory + "M/" + maxMemory + "M");
        }
    }
}
_

このコードの出力:

_1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M
_

重大な何かを実行する場合、通常Errorをキャッチし、syserrに記録し、選択したロギングフレームワークを使用して記録し、リソースを解放してクリーンな方法で終了します。起こりうる最悪のことは何ですか?とにかく、JVMは死にかけています(またはすでに死んでいます)。Errorをキャッチすることで、少なくともクリーンアップの可能性があります。

警告は、クリーンアップが可能な場所でのみ、これらのタイプのエラーのキャッチをターゲットにする必要があるということです。 catch(Throwable t) {}をどこでも、またはそのようなナンセンスにブランケットしないでください。

75
Chris

あなたcanそれから回復します:

package com.stackoverflow.q2679330;

public class Test {

    public static void main(String... args) {
        int size = Integer.MAX_VALUE;
        int factor = 10;

        while (true) {
            try {
                System.out.println("Trying to allocate " + size + " bytes");
                byte[] bytes = new byte[size];
                System.out.println("Succeed!");
                break;
            } catch (OutOfMemoryError e) {
                System.out.println("OOME .. Trying again with 10x less");
                size /= factor;
            }
        }
    }

}

しかし、それは理にかなっていますか?他に何をしたいですか?なぜそんなに多くのメモリを最初に割り当てるのですか?少ないメモリでも大丈夫ですか?とにかくそれをすでに利用してみませんか?または、それが不可能な場合、最初からJVMにより多くのメモリを与えるだけではどうですか?

質問に戻ります:

1:Java.lang.OutOfMemoryErrorをキャッチすることは良い考えかもしれませんが、実際のWordシナリオはありますか?

思い浮かぶものはありません。

2:Java.lang.OutOfMemoryErrorをキャッチする場合、catchハンドラーがそれ自体でメモリを割り当てないことをどのように確認できますか(ツールまたはベストプラクティス)?

OOMEの原因に依存します。 tryブロックの外側で宣言されていて、段階的に発生した場合、その可能性はほとんどありません。 may事前にメモリ領域を確保したい場合:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

そして、OOME中にゼロに設定します:

} catch (OutOfMemoryException e) {
     reserve = new byte[0];
     // Ha! 1MB free!
}

もちろん、これはまったく意味がありません;)アプリケーションが必要とするJVMに十分なメモリを与えてください。必要に応じてプロファイラーを実行します。

29
BalusC

一般に、OOMをキャッチして回復しようとするのは悪い考えです。

  1. OOMEは、アプリケーションが認識していないスレッドを含む他のスレッドにもスローされる可能性があります。そのようなスレッドはすべてデッドになり、通知を待っていたものは永久にスタックする可能性があります。要するに、アプリが最終的に破損する可能性があります。

  2. 正常に回復したとしても、JVMがヒープ不足に苦しんでいる可能性があり、その結果、アプリケーションはわずかに実行されます。

OOMEを使用する最善の方法は、JVMを停止させることです。

(これは、JVM does dieと仮定します。たとえば、TomcatサーブレットスレッドのOOMはJVMを強制終了せず、Tomcatは要求に応答しない緊張状態になります。 ...再起動のリクエストもありません。)

[〜#〜] edit [〜#〜]

OOMをキャッチするのは悪い考えだとは言っていません。問題は、OOMEから意図的にまたは監視によって回復しようとすると発生します。 OOMを(直接、またはErrorまたはThrowableのサブタイプとして)キャッチするたびに、それを再スローするか、アプリケーション/ JVMが終了するように調整する必要があります。

余談:これは、OOMに直面して最大限の堅牢性を得るには、アプリケーションが Thread.setDefaultUncaughtExceptionHandler() を使用して、OOMEのイベントでアプリケーションが終了するハンドラーを設定する必要があることを示唆していますOOMEがスローされるスレッド。これに関する意見に興味があります...

他の唯一のシナリオは、確かにを知っているときです。OOMが副次的な損傷をもたらしていないことです。つまり、あなたは知っています:

  • oOMEを特に引き起こしたのは、
  • その時点でアプリケーションが何をしていたか、その計算を単純に破棄してもよいこと、および
  • (大まかに)同時OOMEが別のスレッドで発生することはありえないこと。

これらのことを知ることができるアプリケーションがありますが、ほとんどのアプリケーションでは、OOMEの後の継続が安全であることを確実に知ることはできません。試してみると経験的に「機能する」場合でも。

(問題は、「予期される」OOMEの結果が安全であり、「予期しない」OOMEがtry/catch OOMEの制御内で発生しないことを示すために、正式な証拠が必要なことです。)

14
Stephen C

はい、現実のシナリオがあります。私の場合:ノードごとのメモリが限られているクラスターで非常に多くのアイテムのデータセットを処理する必要があります。特定のJVMインスタンスは多数のアイテムを順番に処理しますが、一部のアイテムは大きすぎてクラスターで処理できません。OutOfMemoryErrorをキャッチして、どのアイテムが大きすぎるかを確認できます。後で、より多くのRAMを搭載したコンピューターで大きなアイテムだけを再実行できます。

(アレイの単一のマルチギガバイト割り当てが失敗するため、JVMはエラーをキャッチした後でも問題なく、他のアイテムを処理するのに十分なメモリがあります。)

13
Michael Kuhn

OOMEをキャッチすることが理にかなっているシナリオは間違いなくあります。 IDEAこれらをキャッチし、起動メモリ設定を変更するためのダイアログをポップアップします(完了したら終了します)。アプリケーションサーバーがそれらをキャッチして報告します。これを行うための鍵はディスパッチで高レベルでそれを行うと、例外をキャッチしている時点で大量のリソースが解放される可能性が十分にあります。

上記のIDEAシナリオに加えて、一般にキャッチはOOMだけでなくThrowableである必要があり、少なくともスレッドが間もなく終了するコンテキストで行われる必要があります。

もちろん、ほとんどの場合、メモリは枯渇しており、状況は回復できませんが、それが意味をなす方法があります。

10
Yishai

私の場合、OutOfMemoryErrorをキャッチするのが良い考えかどうか疑問に思っていたので、この質問に出会いました。私はここで部分的に答えて、このエラーをキャッチすることが誰か(つまり私)にとって意味のある別の例を示し、部分的にそれが私のケースで本当に良いアイデアであるかどうかを見つけます(私は非常に若い開発者です私が書いたコードの単一行についてあまりにも確認してください)。

とにかく、私はAndroidさまざまなメモリサイズのさまざまなデバイスで実行できるアプリケーションに取り組んでいます。危険な部分は、ファイルからビットマップをデコードし、ImageViewインスタンスに配置することです。デコードされたビットマップのサイズに関してより強力なデバイスを制限したり、非常に少ないメモリで出会ったことのない古代のデバイスでアプリが実行されないことを確認することもできません。 :

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

このようにして、ユーザーのニーズや期待に応じて、より強力なデバイスとそうでないデバイスを提供することができます。

8
Maria

はい、本当の質問は「例外ハンドラで何をするつもりですか」です。有用なほぼすべての場合、より多くのメモリを割り当てます。 OutOfMemoryErrorが発生したときに診断作業を行いたい場合は、-XX:OnOutOfMemoryError=<cmd> HotSpot VMが提供するフック。 OutOfMemoryErrorが発生するとコマンドを実行し、Javaのヒープ外で何か便利なことができます。そもそもアプリケーションがメモリ不足にならないようにしたいので、なぜそれが起こるのかを理解することが最初のステップです。その後、必要に応じてMaxPermSizeのヒープサイズを増やすことができます。他の便利なHotSpotフックを次に示します。

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

完全なリストを参照してください here

5
Rob Heiser

OutOfMemoryError障害から回復する必要があるアプリケーションがあり、シングルスレッドプログラムでは常に動作しますが、マルチスレッドプログラムでは動作しない場合があります。アプリケーションは自動化されたJavaテストクラスで生成されたテストシーケンスを可能な限り深く実行するテストツールです。UIは安定している必要がありますが、テストエンジンは成長中にメモリ不足になりますテストケースのツリー:テストエンジンでは、次のようなコードイディオムでこれを処理します。

 boolean isOutOfMemory = false; //レポートに使用されるフラグ
 try {
 SomeType largeVar; 
 // largeVar 
により多くを割り当てるメインループ// OKを終了するか、レイズするOutOfMemoryError 
} 
 catch(OutOfMemoryError ex){
 // largeVarはスコープ外になったため、garbage 
 System.gc(); // largeVarデータをクリーンアップします
 isOutOfMemory = true; //使用可能なフラグ
} 
 //リカバリを報告するためのプログラムテストフラグ

これは、シングルスレッドアプリケーションで常に機能します。しかし、最近、テストエンジンをUIとは別のワーカースレッドに配置しました。現在、メモリ不足はいずれかのスレッドで任意に発生する可能性があり、それをどのようにキャッチするかは明確ではありません。

たとえば、UIのアニメーションGIFのフレームが、制御できないSwingクラスによって舞台裏で作成された独自のスレッドによって循環されている間に、OOMEが発生しました。必要なすべてのリソースを事前に割り当てたと思っていましたが、次の画像を取得するたびにアニメーターがメモリを割り当てていることは明らかです。 anyスレッドで発生したOOMEを処理する方法についてアイデアを持っている人がいれば、ぜひ聞いてみてください。

5
Tony Simons

OOMEはキャッチできますが、キャッチに達したときにJVMがいくつかのオブジェクトをガベージコレクションできるかどうか、およびその時点までに残っているヒープメモリの数に応じて、通常は役に立たなくなります。

例:私のJVMでは、このプログラムは最後まで実行されます。

import Java.util.LinkedList;
import Java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

ただし、キャッチに1行追加するだけで、私が話していることがわかります。

import Java.util.LinkedList;
import Java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

キャッチに達すると、JVMはリストがもう使用されないことを検出するため、最初のプログラムは正常に実行されます(この検出はコンパイル時に行われる最適化でもあります)。したがって、printステートメントに到達すると、ヒープメモリはほぼ完全に解放されているので、続行するための大きな余裕があります。これが最良のケースです。

ただし、OOMEがキャッチされた後にリストllが使用されるようにコードが配置されている場合、JVMはそれを収集できません。これは2番目のスニペットで発生します。新しいLong作成によってトリガーされるOOMEはキャッチされますが、すぐに新しいオブジェクト(System.out,println line)、およびヒープがほぼいっぱいであるため、新しいOOMEがスローされます。これは最悪のシナリオです:新しいオブジェクトを作成しようとしましたが、失敗し、OOMEをキャッチしました、はい、しかし新しいヒープメモリを必要とする最初の命令(たとえば、新しいオブジェクトの作成)は新しいOOMEをスローします。考えてみてください。この時点でメモリがほとんど残っていないのに、他に何ができるでしょうか。おそらく終了します。したがって、役に立たない。

JVMがリソースを収集しない理由の1つは、本当に怖いことです。他のスレッドとの共有リソースもそれを利用しています。脳を持っている人なら誰でも、どんな種類の非実験的なアプリに挿入されてもOOMEを捕まえることの危険性を理解できます。

Windows x86 32ビットJVM(JRE6)を使用しています。各Javaアプリのデフォルトメモリは64MBです。

4
Mister Smith

OutOfMemoryErrorをキャッチするのが理にかなっているように見え、動作しているように見えるシナリオがあります。

シナリオ:Androidアプリで、可能な限り最高の解像度で複数のビットマップを表示し、それらをスムーズにズームできるようにしたい。

滑らかなズームのため、ビットマップをメモリに保存したいです。ただし、Androidには、デバイスに依存し、制御が難しいメモリの制限があります。

この状況では、ビットマップの読み取り中にOutOfMemoryErrorが発生する場合があります。ここで、それを捕まえてから低解像度で続行すると役立ちます。

3
Jörg Eisfeld

質問2については、BalusCが提案する解決策を既に見ています。

  1. Java.lang.OutOfMemoryErrorをキャッチするのが良い考えかもしれないときに、実際のWordシナリオはありますか?

良い例に出会ったばかりだと思います。 awtアプリケーションがメッセージをディスパッチすると、キャッチされていないOutOfMemoryErrorがstderrに表示され、現在のメッセージの処理が停止します。しかし、アプリケーションは実行し続けます!ユーザーは、舞台裏で発生する深刻な問題を認識せずに他のコマンドを発行する場合があります。特に、彼が標準誤差を観察できない、または観察しない場合。したがって、oom例外をキャッチして、アプリケーションの再起動を提供する(または少なくとも提案する)ことは望ましいことです。

3
Jarekczek

私がOOMエラーをキャッチする理由を考えることができる唯一の理由は、もう使用していない大規模なデータ構造があり、nullに設定してメモリを解放できることです。しかし、(1)メモリを浪費していることを意味します。OOMEの後に単にリンプするのではなく、コードを修正する必要があります。 OOMはいつでも発生する可能性があり、潜在的にすべてが半分になったままになります。

3
cHao
  1. 「良い」をどのように定義するかに依存します。バグのあるWebアプリケーションでそれを行い、動作しますほとんどの場合(ありがたいことに、今ではOutOfMemoryは無関係な修正のために発生しません)。ただし、キャッチしても、いくつかの重要なコードが破損している可能性があります。複数のスレッドがある場合、それらのいずれかでメモリ割り当てが失敗する可能性があります。そのため、アプリケーションによっては、10〜90%の確率で元に戻せない可能性があります。
  2. 私が理解している限り、途中で重いスタックが巻き戻されると、非常に多くの参照が無効になり、そのため気にする必要のない多くのメモリが解放されます。

編集:試してみることをお勧めします。たとえば、より多くのメモリを徐々に割り当てる関数を再帰的に呼び出すプログラムを作成します。 OutOfMemoryErrorをキャッチして、そのポイントから有意義に続行できるかどうかを確認します。私の経験によれば、あなたはできるでしょうが、私の場合、それはWebLogicサーバーの下で起こったので、何らかの黒魔術が関係していたかもしれません。

0
doublep

Throwableの下では何でもキャッチできます。一般的に、RuntimeExceptionを除くExceptionのサブクラスのみをキャッチする必要があります(ただし、開発者の大部分はRuntimeExceptionもキャッチしますが、それは言語設計者の意図ではありません)。

OutOfMemoryErrorをキャッチした場合、一体どうしますか? VMはメモリ不足です。基本的には、終了するだけです。メモリを消費するため、メモリ不足を通知するダイアログボックスを開くことはできません:-)

VMは、実際にメモリ不足になったときにOutOfMemoryErrorをスローします(実際、すべてのエラーは回復不可能な状況を示すはずです)。実際に対処できることは何もないはずです。

行うべきことは、メモリが不足している理由を調べ(NetBeansのようなプロファイラを使用)、メモリリークがないことを確認することです。メモリリークがない場合は、VMに割り当てるメモリを増やします。

0
TofuBeer

JVMは、OutOfMemoryErrorに対処するための有用な引数を提供しています。この記事では、これらのJVM引数を強調したいと思います。これらのJVM引数は次のとおりです。

  1. -XX:+ HeapDumpOnOutOfMemoryError -XX:HeapDumpPath
  2. -XX:OnOutOfMemoryError
  3. -XX:+ ExitOnOutOfMemoryError
  4. -XX:+ CrashOnOutOfMemoryError

この引数を渡すと、OutOfMemoryErrorがスローされたときにJVMがすぐに終了します。アプリケーションを終了する場合は、この引数を渡すことができます。しかし、個人的には、この議論を構成することは好みません。なぜなら、私たちは常に優雅な出口を達成することを目指すべきだからです。突然の出口は、進行中のトランザクションを危険にさらす可能性があります。

OutOfMemory関連のJVM引数

0
Jim T