web-dev-qa-db-ja.com

StackOverflowErrorから回復できるのはなぜですか?

Javaで StackOverflowError が発生した後でも実行を継続できることに驚いています。

StackOverflowErrorはErrorクラスのサブラスであることを知っています。クラスErrorは、「妥当なアプリケーションがキャッチしようとしてはならない重大な問題を示すThrowableのサブクラス」として非難されています。

StackOverflowErrorのようなエラーをキャッチすることは実際に許可されており、そうしないのはプログラマーの合理性次第です。そして、このコードをテストしたところ、正常に終了します。

public class Test
{
    public static void main(String[] args)
    {
        try {
            foo();
        } catch (StackOverflowError e) {
            bar();
        }
        System.out.println("normal termination");
    }

    private static void foo() {
        System.out.println("foo");
        foo();
    }

    private static void bar() {
        System.out.println("bar");
    }
}

どうすればいいの? StackOverflowErrorがスローされる頃には、スタックは非常にいっぱいになっているはずなので、別の関数を呼び出す余地はありません。エラー処理ブロックは別のスタックで実行されていますか、またはここで何が起こっていますか?

99
user3370796

スタックがオーバーフローしてStackOverflowErrorがスローされると、通常の例外処理によりスタックが巻き戻されます。スタックを解くとは、次のことを意味します。

  • 現在アクティブな関数の実行を中止します
  • スタックフレームを削除し、呼び出し関数を続行します
  • 呼び出し元の実行を中止する
  • スタックフレームを削除し、呼び出し関数を続行します
  • 等々...

...例外がキャッチされるまで。これは正常であり(実際、必要です)、どの例外がスローされ、その理由とは無関係です。 foo()の最初の呼び出し以外で例外をキャッチしたため、スタックを埋めた数千のfooスタックフレームはすべて巻き戻され、ほとんどのスタックは自由に再利用できます。

118
user395760

StackOverflowErrorがスローされると、スタックがいっぱいになります。ただし、caughtの場合、これらのfoo呼び出しはすべてスタックからポップされています。スタックがbarsでオーバーフローしなくなったため、fooは正常に実行できます。 (JLSでは、このようなスタックオーバーフローからの回復を保証しているとは思わないことに注意してください。)

StackOverFlowが発生すると、JVMはキャッチまでポップダウンし、スタックを解放します。

あなたの例では、スタックされたすべてのfooを取り除きます。

12

スタックは実際にはオーバーフローしないからです。より良い名前はAttemptToOverflowStackです。基本的にそれが意味することは、スタックに十分な空き領域が残っていないため、スタックフレームを調整する最後の試行が失敗することです。スタックには実際には多くのスペースが残っている可能性がありますが、十分なスペースがありません。したがって、成功する呼び出し(通常はメソッド呼び出し)に依存する操作は、実行されることはなく、プログラムがその事実を処理するだけです。つまり、実際には他の例外と何の違いもありません。実際、呼び出しを行っている関数で例外をキャッチできます。

8
jmoreno

既に回答済み のように、StackOverflowErrorをキャッチした後にコードを実行し、特に関数を呼び出すことができます。これは、JVMの通常の例外処理プロシージャがthrowcatchポイントの間のスタックを巻き戻す、使用するスタックスペースを解放します。そして、あなたの実験はそれが事実であることを確認します。

ただし、一般に、StackOverflowErrorからrecoverを実行できるということとはまったく異なります。

A StackOverflowError IS-A VirtualMachineError 、IS-AN Error 。あなたが指摘しているように、JavaはErrorに対して漠然としたアドバイスを提供します:

合理的なアプリケーションがキャッチしようとしてはならない重大な問題を示します

そして、あなたは、合理的に、shouldErrorをキャッチするように聞こえるが、状況によっては問題ないかもしれないと結論付けます。 1つの実験を実行しても、一般に何かが安全であることを示すわけではないことに注意してください。 Java言語と使用するクラスの仕様のルールのみがそれを行うことができます。VirtualMachineErrorは例外の特別なクラスです。なぜなら、Java Language SpecificationおよびJava Virtual Machine Specificationは、この例外のセマンティクスに関する情報を提供します。特に、 後者は言う

Java仮想マシンの実装は、内部エラーまたはリソースの制限により、この章で説明するセマンティクスの実装を妨げる場合、クラスVirtualMethodErrorのサブクラスのインスタンスであるオブジェクトをスローします。この仕様は予測できません内部エラーまたはリソース制限が発生する可能性があり、それらを報告できるタイミングを正確に規定していません。したがって、以下で定義するVirtualMethodErrorサブクラスは、Java仮想マシン:

...

  • StackOverflowError:Java仮想マシンの実装は、スレッドのスタック領域を使い果たしました。これは、通常、実行プログラムの障害の結果としてスレッドが無限の再帰呼び出しを行っているためです。

重要な問題は、StackOverflowErrorがスローされる場所またはタイミングを「予測できない」ことです。スローされない場所について 無保証 があります。たとえば、メソッドへのentryでスローされることに依存することはできません。メソッド内のポイントでスローされる可能性があります

この予測不可能性は悲惨な可能性があります。メソッド内でスローできるため、クラスが1つの「アトミック」操作と見なす一連の操作の途中でスローされ、オブジェクトを部分的に変更された一貫性のない状態のままにします。一貫性のない状態のオブジェクトでは、anyそのオブジェクトを使用しようとすると、誤った動作が発生する可能性があります。すべての実用的なケースでは、whichオブジェクトが一貫性のない状態にあることがわからないため、noオブジェクトが信頼できると仮定する必要があります。 任意の回復操作または例外がキャッチされた後に続行しようとすると、誤った動作が発生する可能性があります。したがって、唯一安全なことは、notStackOverflowErrorをキャッチすることですが、むしろプログラムの終了を許可することです。 (実際には、トラブルシューティングを支援するためにいくつかのエラーロギングを試みることもできますが、正しく動作するそのロギングをrelyすることはできません)。つまり、StackOverflowErrorから確実に回復することはできません。

2
Raedwald