16

StackOverflowErrorJavaでどのように処理できますか?

4

14 に答える 14

21

「ハンドル」の意味がわかりません。

あなたは確かにそのエラーをキャッチすることができます:

public class Example {
    public static void endless() {
        endless();
    }

    public static void main(String args[]) {
        try {
            endless();
        } catch(StackOverflowError t) {
            // more general: catch(Error t)
            // anything: catch(Throwable t)
            System.out.println("Caught "+t);
            t.printStackTrace();
        }
        System.out.println("After the error...");
    }
}

しかし、自分が何をしているのかを正確に理解していない限り、それはおそらく悪い考えです。

于 2009-06-04T16:51:46.817 に答える
18

おそらく、いくつかの無限再帰が行われています。

つまり、自分自身を何度も呼び出すメソッド

public void sillyMethod()
{
    sillyMethod();
}

これを処理する 1 つは、コードを修正して、再帰が永遠に続くのではなく終了するようにすることです。

于 2009-06-04T16:31:34.823 に答える
13

Raymond Chenの投稿をご覧ください。スタック オーバーフローをデバッグするときは、繰り返される再帰部分に注目する必要があります。抜粋:

これが既知の問題であるかどうかを確認するために欠陥追跡データベースを調べた場合、スタックのトップ関数を検索しても興味深いものは見つからない可能性があります。これは、スタック オーバーフローが再帰のランダムなポイントで発生する傾向があるためです。各スタック オーバーフローは、同じスタック オーバーフローであっても、表面的には他のすべてのスタック オーバーフローとは異なって見えます。

フレール・ジャックの歌を歌っているとします。ただし、各節を前の節よりも数トーン高く歌っています。最終的には、歌唱範囲の上限に到達しますが、それがどこで起こるかは、メロディに対して声域の限界がどこにあるかによって異なります。メロディーでは、最初の 3 つの音はそれぞれ新しい「記録的な高さ」 (つまり、これまでに歌われたどの音よりも高い音) であり、新記録の高さは 3 小節目の 3 つの音に現れ、最後の記録です。 5 小節目の 2 番目の音が高くなります。

メロディーがプログラムのスタック使用量を表している場合、スタック オーバーフローは、プログラムの実行中にこれら 5 つの場所のいずれかで発生する可能性があります。言い換えれば、同じ根底にある暴走再帰 (音楽的にはメロディーのより高い演奏によって表される) が、5 つの異なる方法で現れる可能性があります。このアナロジーでの「再帰」はかなり速く、ループが繰り返されるわずか 8 小節前です。実際には、ループが非常に長くなる可能性があり、スタック オーバーフローが発生する可能性のある数十のポイントが発生する可能性があります。

スタック オーバーフローに直面した場合は、スタックの一番上を無視する必要があります。これは、声域を超えた特定の音に焦点を合わせているだけだからです。根本的な原因が同じであるすべてのスタック オーバーフローに共通するのは、メロディー全体を見つけることです。

于 2009-06-04T16:54:33.270 に答える
7

「-Xss」オプションがJVMでサポートされているかどうかを確認することをお勧めします。その場合は、512k(32ビットWindowsおよびUnixではデフォルトは256k)の値に設定してみて、それが何かを行うかどうかを確認することをお勧めします(StackOverflowExceptionまで長く座る以外)。これはスレッドごとの設定であることに注意してください。したがって、多数のスレッドを実行している場合は、ヒープ設定を強化することもできます。

于 2009-06-04T17:21:25.310 に答える
4

正解はすでに与えられたものです。おそらく、a)コードにバグがあり、通常は診断と修正が非常に簡単な無限再帰につながるか、b)非常に深い再帰につながる可能性のあるコードがあります。たとえば、不均衡な二分木を再帰的にトラバースします。後者の状況では、スタックに情報を割り当てないように(つまり、再帰しないように)コードを変更する必要がありますが、代わりにヒープに情報を割り当てます。

たとえば、不均衡なツリートラバーサルの場合、再訪する必要のあるノードをスタックデータ構造に格納できます。順序トラバーサルの場合は、左側のブランチをループダウンして、アクセスしたときに各ノードを押して、リーフに到達するまで処理します。次に、ノードをスタックの一番上からポップして処理し、ループを再開します。右の子(ループ変数を右のノードに設定するだけです。)これは、スタック上にあったすべてのものをスタックデータ構造のヒープに移動することにより、一定量のスタックを使用します。ヒープは通常、スタックよりもはるかに豊富です。

通常は非常に悪い考えですが、メモリの使用が極端に制限されている場合に必要なものとして、ポインタの反転を使用できます。この手法では、スタックをトラバースしている構造にエンコードし、トラバースしているリンクを再利用することで、追加のメモリをまったく使用せずに、または大幅に少なくしてこれを行うことができます。上記の例を使用すると、ループ時にノードをプッシュする代わりに、直接の親を覚えておく必要があります。各反復で、トラバースしたリンクを現在の親に設定し、次に現在の親を離れるノードに設定します。葉に着いたら、それを処理し、次に親に行き、それから私たちは難問を抱えています。左側のブランチを修正してこのノードを処理して右側のブランチを続行するか、右側のブランチを修正して親に移動するかはわかりません。したがって、反復するときに追加の情報を割り当てる必要があります。通常、この手法の低レベルの実現では、そのビットはポインタ自体に格納され、全体として追加のメモリや一定のメモリは発生しません。これはJavaのオプションではありませんが、他の目的で使用されるフィールドでこのビットを削除することは可能かもしれません。最悪の場合でも、これは必要なメモリ量の少なくとも32倍または64倍の削減になります。もちろん、このアルゴリズムは完全に紛らわしい結果で間違いを犯しやすく、並行性で完全な大混乱を引き起こします。したがって、メモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢の価値はほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。この手法を低レベルで実現する場合、そのビットはポインタ自体に格納されるため、追加のメモリはなく、全体的に一定のメモリになります。これはJavaのオプションではありませんが、他の目的で使用されるフィールドでこのビットを削除することは可能かもしれません。最悪の場合でも、これは必要なメモリ量の少なくとも32倍または64倍の削減になります。もちろん、このアルゴリズムは完全に紛らわしい結果で間違いを犯しやすく、並行性で完全な大混乱を引き起こします。したがって、メモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢の価値はほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。この手法を低レベルで実現する場合、そのビットはポインタ自体に格納されるため、追加のメモリはなく、全体的に一定のメモリになります。これはJavaのオプションではありませんが、他の目的で使用されるフィールドでこのビットを削除することは可能かもしれません。最悪の場合でも、これは必要なメモリ量の少なくとも32倍または64倍の削減になります。もちろん、このアルゴリズムは完全に紛らわしい結果で間違いを犯しやすく、並行性で完全な大混乱を引き起こします。したがって、メモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢の価値はほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。しかし、他のことに使用されるフィールドでこのビットをリスすることは可能かもしれません。最悪の場合でも、これは必要なメモリ量の少なくとも32倍または64倍の削減になります。もちろん、このアルゴリズムは完全に紛らわしい結果で間違いを犯しやすく、並行性で完全な大混乱を引き起こします。したがって、メモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢の価値はほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。しかし、他のことに使用されるフィールドでこのビットをリスすることは可能かもしれません。最悪の場合でも、これは必要なメモリ量の少なくとも32倍または64倍の削減になります。もちろん、このアルゴリズムは完全に紛らわしい結果で間違いを犯しやすく、並行性で完全な大混乱を引き起こします。したがって、メモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢の価値はほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。sメモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢に値することはほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。sメモリの割り当てが不可能な場合を除いて、メンテナンスの悪夢に値することはほとんどありません。典型的な例は、このようなアルゴリズムが一般的なガベージコレクターです。

しかし、私が本当に話したかったのは、StackOverflowErrorを処理したい場合です。つまり、JVMで末尾呼び出しを排除します。1つのアプローチは、末尾呼び出しを実行する代わりにnullaryプロシージャオブジェクトを返す、または値を返すだけの場合はそれを返すトランポリンスタイルを使用することです。[注:これには、関数がAまたはBのいずれかを返すことを示す何らかの手段が必要です。Javaでは、おそらくこれを行う最も簡単な方法は、一方の型を通常どおり返し、もう一方を例外としてスローすることです。]次に、メソッドを呼び出すたびに、値を取得するまで、nullaryプロシージャ(nullaryプロシージャまたは値のいずれかを返します)を呼び出すwhileループを実行する必要があります。無限ループは、プロシージャオブジェクトを返すプロシージャオブジェクトを常に強制するwhileループになります。トランポリンスタイルの利点は、すべての末尾呼び出しを適切に排除した実装で使用するよりも一定の係数のスタックのみを使用し、非末尾呼び出しには通常のJavaスタックを使用し、変換は単純であり、拡張するだけであるということです。 (面倒な)定数係数でコーディングします。欠点は、すべてのメソッド呼び出しにオブジェクトを割り当て(すぐにガベージになります)、これらのオブジェクトを消費するには、末尾呼び出しごとに2、3の間接呼び出しが必要になることです。

理想的なことは、そもそもこれらのnullプロシージャやその他のものを割り当てないことです。これは、末尾呼び出しの除去が達成することとまったく同じです。ただし、Javaが提供するものを使用して作業する場合、コードを通常どおりに実行し、スタックが不足した場合にのみこれらのnullプロシージャを作成することができます。今でもこれらの役に立たないフレームを割り当てていますが、ヒープではなくスタックで割り当て、一括で割り当てを解除しています。また、呼び出しは通常の直接Java呼び出しです。この変換を説明する最も簡単な方法は、最初にすべてのマルチ呼び出しステートメントメソッドを2つの呼び出しステートメントを持つメソッドに書き直すことです。つまり、fgh(){f(); g(); h(); }はfgh(){f();になります。gh(); }およびgh(){g(); h(); }。簡単にするために、すべてのメソッドが末尾呼び出しで終わると仮定します。これは、メソッドの残りの部分を別のメソッドにパッケージ化するだけで調整できますが、実際には、これらを直接処理する必要があります。これらの変換の後、3つのケースがあります。メソッドの呼び出しがゼロの場合は何もしません。または、メソッドの呼び出しが1つ(末尾)の場合は、同じようにtry-catchブロックでラップします。 2呼び出しの場合の末尾呼び出し。最後に、非末尾呼び出しと末尾呼び出しの2つの呼び出しがある場合があります。その場合、例で示す次の変換を適用します(C#のラムダ表記を使用して、ある程度の成長を伴う匿名の内部クラスに簡単に置き換えることができます)。または、1つの(末尾)呼び出しがあります。この場合、2つの呼び出しの場合の末尾呼び出しと同じように、try-catchブロックでラップします。最後に、非末尾呼び出しと末尾呼び出しの2つの呼び出しがある場合があります。その場合、例で示す次の変換を適用します(C#のラムダ表記を使用して、ある程度の成長を伴う匿名の内部クラスに簡単に置き換えることができます)。または、1つの(末尾)呼び出しがあります。この場合、2つの呼び出しの場合の末尾呼び出しと同じように、try-catchブロックでラップします。最後に、非末尾呼び出しと末尾呼び出しの2つの呼び出しがある場合があります。その場合、例で示す次の変換を適用します(C#のラムダ表記を使用して、ある程度の成長を伴う匿名の内部クラスに簡単に置き換えることができます)。

// top-level handler
Action tlh(Action act) {
    return () => {
        while(true) {
            try { act(); break; } catch(Bounce e) { tlh(() => e.run())(); }
        }
    }
}

gh() {
    try { g(); } catch(Bounce e) { 
        throw new Bounce(tlh(() => { 
            e.run(); 
            try { h(); } catch(StackOverflowError e) {
                throw new Bounce(tlh(() => h());
            }
        }); 
    }
    try { h(); } catch(StackOverflowError e) { 
        throw new Bounce(tlh(() => h())); 
    }
}

ここでの主な利点は、例外がスローされない場合です。これは、いくつかの追加の例外ハンドラーをインストールしただけで開始したコードと同じです。末尾呼び出し(h()呼び出し)はバウンス例外を処理しないため、その例外はそれらを通過して、スタックからそれらの(不要な)フレームを巻き戻します。非末尾呼び出しはバウンス例外をキャッチし、残りのコードを追加してそれらを再スローします。これにより、スタックが最上位レベルまで巻き戻され、末尾呼び出しフレームが削除されますが、nullaryプロシージャの非末尾呼び出しフレームは記憶されます。最終的にトップレベルのBounce例外でプロシージャを実行すると、すべての非末尾呼び出しフレームが再作成されます。この時点で、すぐに再びスタックが不足すると、StackOverflowErrorハンドラーを再インストールしないため、必要に応じてキャッチされなくなります。私たちは本当にスタックから外れているので。さらに少し進むと、必要に応じて新しいStackOverflowErrorがインストールされます。さらに、進歩したが、スタックが再び不足した場合、すでに巻き戻したフレームを巻き戻すメリットはないため、新しいトップレベルハンドラーをインストールして、スタックが巻き戻されるだけになるようにします。

このアプローチの最大の問題は、通常のJavaメソッドを呼び出したいと思うかもしれず、呼び出すときにスタックスペースが任意に少ない可能性があるため、開始するのに十分なスペースがあるが終了しない可能性があり、で再開できないことです。真ん中。これには少なくとも2つの解決策があります。1つ目は、そのようなすべての作業を、独自のスタックを持つ別のスレッドに出荷することです。これは非常に効果的で非常に簡単で、同時実行性は導入されません(必要な場合を除く)。別のオプションは、スタックの直前にStackOverflowErrorをスローするだけで、通常のJavaメソッドを呼び出す前に意図的にスタックを巻き戻すことです。再開したときにスタックスペースがまだ不足している場合は、最初から失敗しました。

同様のことを実行して、継続をジャストインタイムにすることもできます。残念ながら、この変換はJavaで手動で行うのは実際には耐えられず、おそらくC#やScalaなどの言語の境界線です。したがって、このような変換は、人ではなく、JVMを対象とする言語によって行われる傾向があります。

于 2012-07-05T01:29:14.490 に答える
1

場合によっては、StackOverflowError をキャッチできないことがあります。

挑戦するたびに、新しいものに出会うでしょう。それはJava VMだからです。Andrew Bullock が言ったように、再帰的なコード ブロックを見つけるのは良いことです。

于 2014-06-24T03:46:10.117 に答える
1

できないと思います-または、少なくとも使用するjvmに依存します。スタック オーバーフローとは、ローカル変数を格納してアドレスを返す余地がないことを意味します。jvm が何らかの形式のコンパイルを行う場合、jvm にもスタックオーバーフローがあり、それを処理したりキャッチしたりすることはできません。jvm は終了する必要があります。

このような動作を可能にする jvm を作成する方法はあるかもしれませんが、遅くなります。

jvm で動作をテストしていませんが、.net ではスタックオーバーフローを処理できません。キャッチしようとしても役に立ちません。Java と .net は同じ概念 (jit を使用した仮想マシン) に依存しているため、Java も同じように動作すると思われます。.NET に stackoverflow-exception が存在することは、プログラムがそれをキャッチできるようにする vm が存在する可能性があることを示唆していますが、通常はそうではありません。

于 2009-06-04T16:31:44.420 に答える
1

取得する可能性が最も高いStackOverflowErrorのは、再帰関数で [長い/無限] 再帰を使用することです。

スタック可能なデータ オブジェクトを使用するようにアプリケーションの設計を変更することで、関数の再帰を回避できます。再帰コードを反復コード ブロックに変換するコーディング パターンがあります。以下の回答をご覧ください。

そのため、独自のデータ スタックを使用することで、劣性関数呼び出しに対する Java によるメモリ スタックを回避できます。

于 2013-06-01T06:09:52.950 に答える
0

単純、

StackOverflowErrorが生成するスタックトレースを見て、コード内のどこで発生するかを把握し、それを使用して、コードが再帰的に呼び出されないようにコードを書き直す方法を理解します(エラーの原因となる可能性があります)。再び起こります。

StackOverflowErrorsは、try ... catch句を介して処理する必要があるものではありませんが、コードのロジックにある基本的な欠陥を示しており、修正する必要があります。

于 2009-06-04T20:18:46.453 に答える
0

このスレッドで多くの人が言及しているように、これの一般的な原因は、終了しない再帰的なメソッド呼び出しです。可能であればスタック オーバーフローを回避し、テストでこれを行う場合は、ほとんどの場合、これを重大なバグと見なす必要があります。場合によっては、Java のスレッド スタック サイズをより大きく設定して、いくつかの状況 (ローカル スタック ストレージで管理されている大規模なデータ セット、長い再帰呼び出し) を処理できますが、これにより全体的なメモリ フットプリントが増加し、数の問題につながる可能性があります。 VM で使用可能なスレッドの数。一般に、この例外が発生した場合、スレッドとこのスレッドへのローカル データはトーストされ、使用されていないと見なされます (つまり、疑わしく、破損している可能性があります)。

于 2009-06-04T16:47:18.693 に答える
0

スタック トレースは、問題の性質を示しているはずです。スタック トレースを読み取ると、明らかなループが発生するはずです。

バグでない場合は、再帰が深すぎてスタック オーバーフローが発生する前に再帰を停止するためのカウンターまたはその他のメカニズムを追加する必要があります。

この例としては、再帰呼び出しを使用して DOM モデルでネストされた XML を処理していて、XML が深くネストされているため、ネストされた呼び出しでスタック オーバーフローが発生する場合が考えられます (可能性は低いですが、可能性はあります)。ただし、スタック オーバーフローを引き起こすには、かなり深いネスティングが必要です。

于 2009-06-04T16:41:53.397 に答える
0

java.lang.Error javadoc:

Error は、Throwable のサブクラスであり、合理的なアプリケーションがキャッチしようとすべきではない重大な問題を示します。このようなエラーのほとんどは異常な状態です。ThreadDeath エラーは、「通常の」状態ですが、ほとんどのアプリケーションがキャッチしようとしないため、Error のサブクラスでもあります。メソッドの throws 句で、メソッドの実行中にスローされる可能性があるがキャッチされない Error のサブクラスを宣言する必要はありません。これらのエラーは決して発生してはならない異常な状態であるためです。

だから、しないでください。コードのロジックで何が問題なのかを見つけてください。この例外は、無限再帰のために頻繁に発生します。

于 2011-01-10T14:42:14.753 に答える
-1
/*
Using Throwable we can trap any know error in JAVA..
*/
public class TestRecur {
    private int i = 0;


    public static void main(String[] args) {
        try {
            new TestRecur().show();
        } catch (Throwable err) {
            System.err.println("Error...");
        }
    }

    private void show() {
        System.out.println("I = " + i++);
        show();
    }
}

ただし、次のリンクを参照してください: http://marxsoftware.blogspot.in/2009/07/diagnosing-and-resolving.htmlコード スニペットを理解すると、エラーが発生する可能性があります。

于 2012-12-04T10:20:03.060 に答える