StackOverflowError は、遭遇する可能性のある最も一般的なランタイム エラーの 1 つであるため、Java 開発者にとって迷惑な場合があります。この記事では、さまざまなコード例を見て、このエラーがどのように発生するのか、そしてその対処方法を学びます。 Stack Frames と StackOverflowerError が発生する仕組み 基本的なことから始めましょう。メソッドが呼び出されると、呼び出しスタック上に新しいスタック フレームが作成されます。スタック フレームには、呼び出されたメソッドのパラメータとそのローカル変数が含まれます。
StackOverflowError は、遭遇する可能性のある最も一般的な実行時エラーの 1 つであるため、Java 開発者にとって迷惑な場合があります。
この記事では、さまざまなコード例を見て、このエラーがどのように発生するのか、そしてその対処方法を理解します。
基本的なことから始めましょう。メソッドが呼び出されると、呼び出しスタック上に新しいスタック フレームが作成されます。このスタック フレームには、呼び出されたメソッドのパラメータ、そのローカル変数、およびメソッドの戻りアドレス (呼び出されたメソッドが戻った後にメソッドの実行を継続するポイント) が含まれています。
スタック フレームの作成は、ネストされたメソッド内のメソッド呼び出しの終わりに達するまで続行されます。
このプロセス中に、JVM が新しいスタック フレームを作成するためのスペースがない状況に遭遇すると、StackOverflower
エラーをスローします。
JVM でこれが発生する最も一般的な理由は、終了していない/無限の再帰です。StackOverflowerr の Javadoc の説明では、このエラーは特定のコード部分の深すぎる再帰によって引き起こされると述べられています。
ただし、このエラーの原因は再帰だけではありません。これは、スタックが使い果たされるまでアプリケーションがメソッド内からメソッドを呼び出し続ける状況でも発生する可能性があります。意図的に不適切なコーディング手法に従う開発者はいないため、これはまれな状況です。もう 1 つのまれな理由は、メソッド内のローカル変数が多数であることです。
StackOverflowError は、アプリケーションがクラス間に循環関係を持つように設計されている場合にもスローされる可能性があります。この場合、互いのコンストラクターが繰り返し呼び出されるため、このエラーが発生します。これは再帰の一形態と考えることもできます。
このエラーを引き起こすもう 1 つの興味深いシナリオは、クラスがそのクラスのインスタンス変数として同じクラス内でインスタンス化される場合です。これにより、同じクラスのコンストラクターが何度も (再帰的に) 呼び出され、最終的にはスタック オーバーフロー エラーが発生します。
以下に示す例では、予期しない再帰により、開発者が再帰動作の終了条件を指定するのを忘れたため、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 エラーが発生するシナリオを見てみましょう。クラス間の循環関係。コンストラクター内で相互にインスタンス化する
ClassOne と
ClassTwo を考慮して、循環関係を作成します。
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 のコンストラクターが
ClassTwo と
ClassTwo# をインスタンス化するため、最終的に StackOverflowError になります。 ## のコンストラクターはインスタンス化します。 ClassOne
再び。これはスタックからオーバーフローするまで繰り返し発生します。 次に、クラスがそのクラスのインスタンス変数として同じクラス内でインスタンス化された場合に何が起こるかを見ていきます。
次の例に示すように、
AccountHolder はインスタンス変数 JointaCountHolder
として自身をインスタンス化します。<pre class="brush:java;">public class AccountHolder {
private String firstName;
private String lastName;
AccountHolder jointAccountHolder = new AccountHolder();
}</pre>
When the
このテストに示すように、クラスをインスタンス化すると、コンストラクターへの再帰呼び出しにより StackOverflowError エラーが発生します。 <pre class="brush:java;">public class AccountHolderManualTest {
@Test(expected = StackOverflowError.class)
public void whenInstanciatingAccountHolder_thenThrowsException() {
AccountHolder holder = new AccountHolder();
}
}</pre>
StackOverflowError の解決
先ほどのコード例によって引き起こされるいくつかのスタック トレースを調べてみましょう。
予期された例外宣言が無視された場合、このスタック トレースは
InfiniteCursionWithTerminationConditionManualTest によって生成されます: <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>
ここでは、5 行目が繰り返されていることがわかります。ここで再帰呼び出しが行われます。あとはコードをチェックして、再帰が正しい方法で行われているかどうかを確認するだけです。
これは、
CyclicDependancyManualTest を実行することで得られるスタック トレースです (繰り返しますが、例外は予想されません): <pre class="brush:java;">java.lang.StackOverflowError
at c.b.s.ClassTwo.<init>(ClassTwo.java:9)
at c.b.s.ClassOne.<init>(ClassOne.java:9)
at c.b.s.ClassTwo.<init>(ClassTwo.java:9)
at c.b.s.ClassOne.<init>(ClassOne.java:9)</pre><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 サイトの他の関連記事を参照してください。