私はこれをどう処理するかについて非常に矛盾したことを聞いたことがあり、次のジレンマにこだわっています。
JVMがその時点で一貫性のない状態にあるため、JVMが死ぬように手放すことがベストプラクティスであると常に理解していましたが、ここでは機能していないようです。
OutOfMemoryError
は他のエラーと同じです。 Thread.run()
からエスケープすると、スレッドが停止します。これ以上何もない。また、スレッドが終了すると、GCルートではなくなるため、このスレッドのみが保持するすべての参照はガベージコレクションの対象になります。これは、JVMがOOMEから回復する可能性が非常に高いことを意味します。
一貫性のない状態になる可能性があるため、JVMを強制終了する場合は、これをJava
オプションに追加します。
-XX:OnOutOfMemoryError="kill -9 %p"
%p
は現在のJavaプロセスPIDプレースホルダーです。残りは自己説明です。
もちろん、 OutOfMemoryError
をキャッチして、なんとか処理することもできます。しかし、それはトリッキーです。
バージョン8u92では、Oracle JDKに、OutOfMemoryErrorが発生したときにJVMを終了させるJVMオプションが追加されました。
リリースノート から:
ExitOnOutOfMemoryError-このオプションを有効にすると、メモリ不足エラーが最初に発生したときにJVMが終了します。メモリ不足エラーを処理するのではなく、JVMのインスタンスを再起動する場合に使用できます。
Javaバージョン8u92でVM引数
-XX:+ExitOnOutOfMemoryError
-XX:+CrashOnOutOfMemoryError
追加されました。 リリースノート を参照してください。
ExitOnOutOfMemoryError
このオプションを有効にすると、メモリ不足エラーが最初に発生したときにJVMが終了します。メモリ不足エラーを処理するのではなく、JVMのインスタンスを再起動する場合に使用できます。CrashOnOutOfMemoryError
このオプションを有効にすると、メモリ不足エラーが発生すると、JVMがクラッシュし、テキストとバイナリのクラッシュファイルが生成されます。
拡張リクエスト: JDK-8138745 (パラメーターの命名は間違っていますが、 JDK-815471 、ExitOnOutOfMemoryError
の代わりにExitOnOutOfMemory
)
エラーが発生したら、プログラムを複数の方法で強制的に終了できます。他の人が示唆したように、エラーをキャッチし、必要に応じてその後System.exitを実行できます。ただし、-XX:+ HeapDumpOnOutOfMemoryErrorも使用することをお勧めします。これにより、イベントが生成されると、JVMはアプリケーションのコンテンツを含むメモリダンプファイルを作成します。プロファイルを使用します。EclipseMATで画像を調査することをお勧めします。このようにして、問題の原因を非常にすばやく見つけ、適切に対応します。 Eclipseを使用していない場合、Eclipse MATをスタンドアロン製品として使用できます。 http://wiki.Eclipse.org/index.php/MemoryAnalyzer を参照してください。
プログラムを停止したい場合は、-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"
( ここに記載 )コマンドラインのオプション。アプリケーションのキルスクリプトを指定するだけです。
一般に、アプリケーションを再起動せずにこのエラーを適切に処理することはできませんでした。常にある種のコーナーケースがすり抜けていたため、個人的にはアプリケーションを実際に停止することをお勧めしますが、問題の原因を調査してください。
一般的に、Java.lang.Error
またはOutOfMemoryError
を含むそのサブクラスをキャッチするcatchブロックを記述しないでください。唯一の例外は、Error
をサブクラス化する必要があるときにRuntimeException
のカスタムサブクラスをスローするサードパーティライブラリを使用している場合です。ただし、これは実際にはコードのエラーに対する回避策にすぎません。
Java.lang.Error
の JavaDoc から:
エラーはThrowableのサブクラスであり、合理的なアプリケーションがキャッチしようとしてはならない重大な問題を示します。
OOMEが原因でスレッドの1つが停止した後でもアプリケーションの実行に問題がある場合は、いくつかのオプションがあります。
最初に、残りのスレッドをデーモンスレッドとしてマークできるかどうかを確認する必要があります。 JVMにデーモンスレッドだけが残っている時点があれば、すべてのシャットダウンフックを実行し、可能な限り秩序正しく終了します。これを行うには、スレッドオブジェクトを開始する前に、スレッドオブジェクトでsetDaemon(true)
を呼び出す必要があります。スレッドが実際にフレームワークまたは他のコードによって作成された場合、そのフラグを設定するために別の手段を使用する必要があります。
もう1つのオプションは、キャッチされていない例外ハンドラーを問題のスレッドに割り当て、System.exit()
を呼び出すか、絶対に必要な場合はRuntime.getRuntime().halt()
を呼び出します。シャットダウンフックは実行されないため、haltの呼び出しは非常に危険ですが、特定の状況では、OOMEが既にスローされた場合にSystem.exitが失敗した場合に停止が機能する場合があります。
終了する前に可能な限り最高のデータを提供しようとすることを保証するために、アプリケーション内からすべてのキャッチされない例外を処理することをお勧めします。次に、クラッシュしたときにプロセスを再起動する外部スクリプトを用意します。
public class ExitProcessOnUncaughtException implements UncaughtExceptionHandler
{
static public void register()
{
Thread.setDefaultUncaughtExceptionHandler(new ExitProcessOnUncaughtException());
}
private ExitProcessOnUncaughtException() {}
@Override
public void uncaughtException(Thread t, Throwable e)
{
try {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
System.out.println("Uncaught exception caught"+ " in thread: "+t);
System.out.flush();
System.out.println();
System.err.println(writer.getBuffer().toString());
System.err.flush();
printFullCoreDump();
} finally {
Runtime.getRuntime().halt(1);
}
}
public static void printFullCoreDump()
{
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("\n"+
sdf.format(System.currentTimeMillis())+"\n"+
"All Stack Trace:\n"+
getAllStackTraces()+
"\nHeap\n"+
getHeapInfo()+
"\n");
}
public static String getAllStackTraces()
{
String ret="";
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
for (Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet())
ret+=getThreadInfo(entry.getKey(),entry.getValue())+"\n";
return ret;
}
public static String getHeapInfo()
{
String ret="";
List<MemoryPoolMXBean> memBeans = ManagementFactory.getMemoryPoolMXBeans();
for (MemoryPoolMXBean mpool : memBeans) {
MemoryUsage usage = mpool.getUsage();
String name = mpool.getName();
long used = usage.getUsed();
long max = usage.getMax();
int pctUsed = (int) (used * 100 / max);
ret+=" "+name+" total: "+(max/1000)+"K, "+pctUsed+"% used\n";
}
return ret;
}
public static String getThreadInfo(Thread thread, StackTraceElement[] stack)
{
String ret="";
ret+="\n\""+thread.getName()+"\"";
if (thread.isDaemon())
ret+=" daemon";
ret+=
" prio="+thread.getPriority()+
" tid="+String.format("0x%08x", thread.getId());
if (stack.length>0)
ret+=" in "+stack[0].getClassName()+"."+stack[0].getMethodName()+"()";
ret+="\n Java.lang.Thread.State: "+thread.getState()+"\n";
ret+=getStackTrace(stack);
return ret;
}
public static String getStackTrace(StackTraceElement[] stack)
{
String ret="";
for (StackTraceElement element : stack)
ret+="\tat "+element+"\n";
return ret;
}
}
JVMオプション
-XX:+ExitOnOutOfMemoryError
-XX:+CrashOnOutOfMemoryError
-XX:OnOutOfMemoryError=...
スレッドが使い果たされたためにOutOfMemoryErrorが発生した場合は動作しません( 対応するJDKバグレポート を参照)、ツール jkill を試してみる価値があります。 [〜#〜] jvmti [〜#〜] を介して登録し、メモリまたは使用可能なスレッドが使い果たされるとVMを終了します。
私のテストでは、期待どおりに動作します(JVMオプションがどのように動作することを期待するか)。
スレッドコードをOOMEのtry catchで囲み、そのようなイベントが発生した場合は手動でクリーンアップできます。秘Aは、スレッド関数を別の関数のキャッチにしようとすることです。メモリエラーが発生すると、スタックの一部の領域が解放され、簡単に削除できるようになります。これは、キャッチした直後にいくつかのリソースでガベージコレクション要求を行う場合や、他のスレッドに終了を指示するために死にかけているフラグを設定する場合に機能するはずです。
OOMEのスレッドが停止し、その要素に対して何らかのコレクションを行うと、他のスレッドが正常に終了するための十分な空き領域が必要になります。これは、死ぬ前に問題を記録する機会がある、より優雅な終了です。