Java で StackOverflowError エラーの問題を解決する方法

WBOY
リリース: 2023-05-13 20:49:04
転載
3252 人が閲覧しました

StackOverflowError の概要

StackOverflowError は、遭遇する可能性のある最も一般的なランタイム エラーの 1 つであるため、Java 開発者にとって迷惑な場合があります。この記事では、さまざまなコード例を見て、このエラーがどのように発生するのか、そしてその対処方法を学びます。 Stack Frames と StackOverflowerError が発生する仕組み 基本的なことから始めましょう。メソッドが呼び出されると、呼び出しスタック上に新しいスタック フレームが作成されます。スタック フレームには、呼び出されたメソッドのパラメータとそのローカル変数が含まれます。

StackOverflowError は、遭遇する可能性のある最も一般的な実行時エラーの 1 つであるため、Java 開発者にとって迷惑な場合があります。

この記事では、さまざまなコード例を見て、このエラーがどのように発生するのか、そしてその対処方法を理解します。

Stack Frames と StackOverflowerError が発生する仕組み

基本的なことから始めましょう。メソッドが呼び出されると、呼び出しスタック上に新しいスタック フレームが作成されます。このスタック フレームには、呼び出されたメソッドのパラメータ、そのローカル変数、およびメソッドの戻りアドレス (呼び出されたメソッドが戻った後にメソッドの実行を継続するポイント) が含まれています。

スタック フレームの作成は、ネストされたメソッド内のメソッド呼び出しの終わりに達するまで続行されます。

このプロセス中に、JVM が新しいスタック フレームを作成するためのスペースがない状況に遭遇すると、StackOverflower エラーをスローします。

JVM でこれが発生する最も一般的な理由は、終了していない/無限の再帰です。StackOverflowerr の Javadoc の説明では、このエラーは特定のコード部分の深すぎる再帰によって引き起こされると述べられています。

ただし、このエラーの原因は再帰だけではありません。これは、スタックが使い果たされるまでアプリケーションがメソッド内からメソッドを呼び出し続ける状況でも発生する可能性があります。意図的に不適切なコーディング手法に従う開発者はいないため、これはまれな状況です。もう 1 つのまれな理由は、メソッド内のローカル変数が多数であることです。

StackOverflowError は、アプリケーションがクラス間に循環関係を持つように設計されている場合にもスローされる可能性があります。この場合、互いのコンストラクターが繰り返し呼び出されるため、このエラーが発生します。これは再帰の一形態と考えることもできます。

このエラーを引き起こすもう 1 つの興味深いシナリオは、クラスがそのクラスのインスタンス変数として同じクラス内でインスタンス化される場合です。これにより、同じクラスのコンストラクターが何度も (再帰的に) 呼び出され、最終的にはスタック オーバーフロー エラーが発生します。

StackOverflowError running

以下に示す例では、予期しない再帰により、開発者が再帰動作の終了条件を指定するのを忘れたため、StackOverflowError エラーがスローされます:

public class UnintendedInfiniteRecursion {
    public int calculateFactorial(int number) {
        return number * calculateFactorial(number - 1);
    }
}
ログイン後にコピー

ここで、メソッドに渡される値については、いかなる場合でもエラーが発生します:

public class UnintendedInfiniteRecursionManualTest {
    @Test(expected = <a href="https://javakk.com/tag/stackoverflowerror" rel="external nofollow"  rel="external nofollow"      title="查看更多关于 StackOverflowError 的文章" target="_blank">StackOverflowError</a>.class)
    public void givenPositiveIntNoOne_whenCalFact_thenThrowsException() {
        int numToCalcFactorial= 1;
        UnintendedInfiniteRecursion uir 
          = new UnintendedInfiniteRecursion();
        
        uir.calculateFactorial(numToCalcFactorial);
    }
    
    @Test(expected = StackOverflowError.class)
    public void givenPositiveIntGtOne_whenCalcFact_thenThrowsException() {
        int numToCalcFactorial= 2;
        UnintendedInfiniteRecursion uir 
          = new UnintendedInfiniteRecursion();
        
        uir.calculateFactorial(numToCalcFactorial);
    }
    
    @Test(expected = StackOverflowError.class)
    public void givenNegativeInt_whenCalcFact_thenThrowsException() {
        int numToCalcFactorial= -1;
        UnintendedInfiniteRecursion uir 
          = new UnintendedInfiniteRecursion();
        
        uir.calculateFactorial(numToCalcFactorial);
    }
}
ログイン後にコピー

ただし、次の例では終了条件が指定されていますが、値 -1# の場合は、 ##calculateFactorial() メソッドに渡されると、終了条件が満たされず、終了しない/無限の再帰が発生します:

public class InfiniteRecursionWithTerminationCondition {
    public int calculateFactorial(int number) {
       return number == 1 ? 1 : number * calculateFactorial(number - 1);
    }
}
ログイン後にコピー

この一連のテストは、このシナリオを示します:

public class InfiniteRecursionWithTerminationConditionManualTest {
    @Test
    public void givenPositiveIntNoOne_whenCalcFact_thenCorrectlyCalc() {
        int numToCalcFactorial = 1;
        InfiniteRecursionWithTerminationCondition irtc 
          = new InfiniteRecursionWithTerminationCondition();

        assertEquals(1, irtc.calculateFactorial(numToCalcFactorial));
    }

    @Test
    public void givenPositiveIntGtOne_whenCalcFact_thenCorrectlyCalc() {
        int numToCalcFactorial = 5;
        InfiniteRecursionWithTerminationCondition irtc 
          = new InfiniteRecursionWithTerminationCondition();

        assertEquals(120, irtc.calculateFactorial(numToCalcFactorial));
    }

    @Test(expected = StackOverflowError.class)
    public void givenNegativeInt_whenCalcFact_thenThrowsException() {
        int numToCalcFactorial = -1;
        InfiniteRecursionWithTerminationCondition irtc 
          = new InfiniteRecursionWithTerminationCondition();

        irtc.calculateFactorial(numToCalcFactorial);
    }
}
ログイン後にコピー

この特定のケースでは、終了条件が次のように単純に表現される場合:

public class RecursionWithCorrectTerminationCondition {
    public int calculateFactorial(int number) {
        return number <= 1 ? 1 : number * calculateFactorial(number - 1);
    }
}
ログイン後にコピー

次のテストは、実際にこの状況を示します:

public class RecursionWithCorrectTerminationConditionManualTest {
    @Test
    public void givenNegativeInt_whenCalcFact_thenCorrectlyCalc() {
        int numToCalcFactorial = -1;
        RecursionWithCorrectTerminationCondition rctc 
          = new RecursionWithCorrectTerminationCondition();

        assertEquals(1, rctc.calculateFactorial(numToCalcFactorial));
    }
}
ログイン後にコピー

次に、StackOverflowError エラーが発生するシナリオを見てみましょう。クラス間の循環関係。コンストラクター内で相互にインスタンス化する

ClassOneClassTwo を考慮して、循環関係を作成します。

public class ClassOne {
    private int oneValue;
    private ClassTwo clsTwoInstance = null;
    
    public ClassOne() {
        oneValue = 0;
        clsTwoInstance = new ClassTwo();
    }
    
    public ClassOne(int oneValue, ClassTwo clsTwoInstance) {
        this.oneValue = oneValue;
        this.clsTwoInstance = clsTwoInstance;
    }
}
ログイン後にコピー
public class ClassTwo {
    private int twoValue;
    private ClassOne clsOneInstance = null;
    
    public ClassTwo() {
        twoValue = 10;
        clsOneInstance = new ClassOne();
    }
    
    public ClassTwo(int twoValue, ClassOne clsOneInstance) {
        this.twoValue = twoValue;
        this.clsOneInstance = clsOneInstance;
    }
}
ログイン後にコピー

次に、 ClassOne をインスタンス化しようとすると仮定します。このテストに示すように:

public class CyclicDependancyManualTest {
    @Test(expected = StackOverflowError.class)
    public void whenInstanciatingClassOne_thenThrowsException() {
        ClassOne obj = new ClassOne();
    }
}
ログイン後にコピー

これは、

ClassOne のコンストラクターが ClassTwoClassTwo# をインスタンス化するため、最終的に StackOverflowError になります。 ## のコンストラクターはインスタンス化します。 ClassOne 再び。これはスタックからオーバーフローするまで繰り返し発生します。 次に、クラスがそのクラスのインスタンス変数として同じクラス内でインスタンス化された場合に何が起こるかを見ていきます。

次の例に示すように、

AccountHolder

はインスタンス変数 JointaCountHolder として自身をインスタンス化します。<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:java;">public class AccountHolder { private String firstName; private String lastName; AccountHolder jointAccountHolder = new AccountHolder(); }</pre><div class="contentsignin">ログイン後にコピー</div></div>When the

AccountHolder

このテストに示すように、クラスをインスタンス化すると、コンストラクターへの再帰呼び出しにより StackOverflowError エラーが発生します。 <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:java;">public class AccountHolderManualTest { @Test(expected = StackOverflowError.class) public void whenInstanciatingAccountHolder_thenThrowsException() { AccountHolder holder = new AccountHolder(); } }</pre><div class="contentsignin">ログイン後にコピー</div></div> StackOverflowError の解決

StackOverflowError が発生した場合、これを慎重に行うのが最善です。スタック トレースを調べて行番号の繰り返しパターンを特定します。これにより、問題のある再帰が含まれるコードを見つけることができます。

先ほどのコード例によって引き起こされるいくつかのスタック トレースを調べてみましょう。

予期された例外宣言が無視された場合、このスタック トレースは

InfiniteCursionWithTerminationConditionManualTest

によって生成されます: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:java;">java.lang.StackOverflowError at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5) at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5) at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5) at c.b.s.InfiniteRecursionWithTerminationCondition .calculateFactorial(InfiniteRecursionWithTerminationCondition.java:5)</pre><div class="contentsignin">ログイン後にコピー</div></div>ここでは、5 行目が繰り返されていることがわかります。ここで再帰呼び出しが行われます。あとはコードをチェックして、再帰が正しい方法で行われているかどうかを確認するだけです。

これは、

CyclicDependancyManualTest

を実行することで得られるスタック トレースです (繰り返しますが、例外は予想されません): <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:java;">java.lang.StackOverflowError at c.b.s.ClassTwo.&lt;init&gt;(ClassTwo.java:9) at c.b.s.ClassOne.&lt;init&gt;(ClassOne.java:9) at c.b.s.ClassTwo.&lt;init&gt;(ClassTwo.java:9) at c.b.s.ClassOne.&lt;init&gt;(ClassOne.java:9)</pre><div class="contentsignin">ログイン後にコピー</div></div><p>该堆栈跟踪显示了在循环关系中的两个类中导致问题的行号。ClassTwo的第9行和ClassOne的第9行指向构造函数中试图实例化另一个类的位置。</p> <p>彻底检查代码后,如果以下任何一项(或任何其他代码逻辑错误)都不是错误的原因:</p> <ul class=" list-paddingleft-2"> <li><p>错误实现的递归(即没有终止条件)</p></li> <li><p>类之间的循环依赖关系</p></li> <li><p>在同一个类中实例化一个类作为该类的实例变量</p></li> </ul> <p>尝试增加堆栈大小是个好主意。根据安装的JVM,默认堆栈大小可能会有所不同。</p> <p><code>-Xss 标志可以用于从项目的配置或命令行增加堆栈的大小。

以上がJava で StackOverflowError エラーの問題を解決する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!