これを最もうまく処理する方法について非常に矛盾したことを聞いたことがあり、次のジレンマに悩まされています。
- OOME はスレッドをダウンさせますが、アプリケーション全体をダウンさせるわけではありません
- アプリケーション全体をダウンさせる必要がありますが、スレッドにメモリが残っていないためできません
その時点でJVMが一貫性のない状態にあるため、JVMが停止する可能性があるため、それらを手放すことがベストプラクティスであることを常に理解していましたが、ここでは機能していないようです。
これを最もうまく処理する方法について非常に矛盾したことを聞いたことがあり、次のジレンマに悩まされています。
その時点でJVMが一貫性のない状態にあるため、JVMが停止する可能性があるため、それらを手放すことがベストプラクティスであることを常に理解していましたが、ここでは機能していないようです。
OutOfMemoryError
他のエラーと同じです。そこから逃げるThread.run()
と、スレッドが死んでしまいます。これ以上何もない。また、スレッドが終了すると、スレッドは GC ルートではなくなるため、このスレッドによってのみ保持されるすべての参照がガベージ コレクションの対象となります。これは、JVM が OOME から回復する可能性が非常に高いことを意味します。
JVM が一貫性のない状態にある可能性があるため、何があっても JVM を強制終了したい場合は、次のオプションをjava
オプションに追加します。
-XX:OnOutOfMemoryError="kill -9 %p"
%p
現在の Java プロセス PID プレースホルダーです。残りは自明です。
もちろんOutOfMemoryError
、なんとか捕まえて扱ってみることもできます。しかし、それはトリッキーです。
Java バージョン 8u92 では、VM 引数
-XX:+ExitOnOutOfMemoryError
-XX:+CrashOnOutOfMemoryError
が追加されました。リリース ノートを参照してください。
ExitOnOutOfMemoryError
このオプションを有効にすると、メモリー不足エラーが最初に発生した時点で JVM が終了します。メモリ不足エラーを処理するよりも、JVM のインスタンスを再起動したい場合に使用できます。CrashOnOutOfMemoryError
このオプションが有効な場合、メモリ不足エラーが発生すると、JVM がクラッシュし、テキストおよびバイナリ クラッシュ ファイルが生成されます。
機能強化のリクエスト: JDK-8138745 (パラメータの命名が間違っていますが 、ではなくJDK-8154713 )ExitOnOutOfMemoryError
ExitOnOutOfMemory
エラーが発生したら、複数の方法でプログラムを強制終了できます。他の人が示唆しているように、必要に応じて、エラーをキャッチし、その後 System.exit を実行できます。ただし、-XX:+HeapDumpOnOutOfMemoryError も使用することをお勧めします。この方法では、イベントが生成されると、JVM はアプリケーションのコンテンツを含むメモリ ダンプ ファイルを作成します。プロファイルを使用します。画像を調査するには、Eclipse MAT をお勧めします。これにより、問題の原因がすぐにわかり、適切に対応できます。Eclipse を使用していない場合は、Eclipse MAT をスタンドアロン製品として使用できます。 http://wiki.eclipse.org/index.php/MemoryAnalyzerを参照してください。
プログラムを停止したい場合は、コマンド ラインの-XX:OnOutOfMemoryError="<cmd args>;<cmd args>"
(ここに記載されている) オプションを参照してください。アプリケーションの kill スクリプトを指定するだけです。
一般に、アプリケーションを再起動せずにこのエラーを適切に処理できたことは一度もありません。ある種のまれなケースが常にすり抜けていたので、個人的にはアプリケーションを実際に停止して問題の原因を調査することをお勧めします。
キャッチされなかったすべての例外をアプリケーション内から処理して、アプリケーションが終了する前に可能な限り最良のデータを提供しようとすることをお勧めします。次に、プロセスがクラッシュしたときにプロセスを再起動する外部スクリプトを作成します。
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;
}
}
java.lang.Error
一般的に言えば、catchや を含むそのサブクラスを含むcatch ブロックを書くべきではありませんOutOfMemoryError
。これに対する唯一の例外は、サブクラス化Error
する必要があるときにカスタム サブクラスをスローするサードパーティ ライブラリを使用している場合ですRuntimeException
。ただし、これは実際にはコード内のエラーの回避策にすぎません。
のJavaDocからjava.lang.Error
:
Error は、Throwable のサブクラスであり、合理的なアプリケーションがキャッチしようとすべきではない重大な問題を示します。
OOME が原因でスレッドの 1 つが終了した後でもアプリケーションが実行され続けるという問題がある場合は、いくつかのオプションがあります。
まず、残りのスレッドをデーモン スレッドとしてマークできるかどうかを確認します。デーモン スレッドだけが JVM に残っている時点があれば、すべてのシャットダウン フックが実行され、可能な限り正常に終了します。これを行うsetDaemon(true)
には、開始する前にスレッド オブジェクトを呼び出す必要があります。スレッドが実際にフレームワークまたはその他のコードによって作成された場合、別の手段を使用してそのフラグを設定する必要がある場合があります。
もう 1 つのオプションは、キャッチされていない例外ハンドラーを問題のスレッドに割り当て、System.exit()
絶対に必要な場合は またはを呼び出すことRuntime.getRuntime().halt()
です。シャットダウン フックが実行を試みさえしないため、halt の呼び出しは非常に危険ですが、特定の状況では、OOME が既にスローされている場合に System.exit が失敗する場合に、halt が機能する可能性があります。
スレッド コードを OOME の try キャッチで囲み、そのようなイベントが発生した場合に手動でクリーンアップを行うことができます。トリックは、スレッド関数を別の関数の try キャッチのみにすることです。メモリエラーが発生すると、スタック上のスペースが解放され、すばやく削除できるようになります。これは、キャッチした直後に一部のリソースでガベージ コレクション リクエストを実行したり、他のスレッドに終了するように伝えるために死にかけているフラグを設定したりする場合に機能するはずです。
OOME を含むスレッドが停止し、その要素のコレクションを実行すると、他のスレッドが正常に終了するのに十分な空き領域が必要になります。これは、死ぬ前に問題をログに記録する機会を持つ、より優雅な終了です。