StackOverflowError kann für Java-Entwickler ärgerlich sein, da es einer der häufigsten Laufzeitfehler ist, auf die wir stoßen können. In diesem Artikel erfahren Sie anhand verschiedener Codebeispiele, wie dieser Fehler auftritt und wie Sie damit umgehen. Wie Stack Frames und StackOverflowerError auftreten Beginnen wir mit den Grundlagen. Wenn eine Methode aufgerufen wird, wird ein neuer Stapelrahmen auf dem Aufrufstapel erstellt. Der Stack-Frame enthält die Parameter der aufgerufenen Methode und ihrer lokalen Variablen.
StackOverflowError kann für Java-Entwickler ärgerlich sein, da es einer der häufigsten Laufzeitfehler ist, auf die wir stoßen können.
In diesem Artikel werden wir anhand verschiedener Codebeispiele verstehen, wie dieser Fehler auftritt und wie man damit umgeht.
Beginnen wir mit den Grundlagen. Wenn eine Methode aufgerufen wird, wird ein neuer Stapelrahmen auf dem Aufrufstapel erstellt. Dieser Stapelrahmen enthält die Parameter der aufgerufenen Methode, ihre lokalen Variablen und die Rücksprungadresse der Methode. Dies ist der Punkt, an dem die Methodenausführung nach der Rückkehr der aufgerufenen Methode fortgesetzt werden soll.
Die Erstellung von Stapelrahmen wird fortgesetzt, bis das Ende des Methodenaufrufs innerhalb der verschachtelten Methode erreicht ist.
Wenn die JVM während dieses Vorgangs auf eine Situation stößt, in der kein Platz zum Erstellen eines neuen Stapelrahmens vorhanden ist, wird ein StackOverflower
-Fehler ausgegeben. StackOverflower
错误。
JVM遇到这种情况的最常见原因是未终止/无限递归——StackOverflowerr的Javadoc描述提到,错误是由于特定代码段中的递归太深而引发的。
然而,递归并不是导致此错误的唯一原因。在应用程序不断从方法内调用方法直到堆栈耗尽的情况下,也可能发生这种情况。这是一种罕见的情况,因为没有开发人员会故意遵循糟糕的编码实践。另一个罕见的原因是方法中有大量局部变量。
当应用程序设计为类之间具有循环关系时,也可以抛出StackOverflowError。在这种情况下,会重复调用彼此的构造函数,从而引发此错误。这也可以被视为递归的一种形式。
另一个引起此错误的有趣场景是,如果一个类在同一个类中作为该类的实例变量实例化。这将导致一次又一次(递归)调用同一类的构造函数,最终导致堆栈溢出错误。
在下面所示的示例中,由于意外递归,开发人员忘记为递归行为指定终止条件,将抛出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(); } }
这最终导致了StackOverflowError错误,因为 ClassOne
的构造函数实例化了 ClassTwo
,而 ClassTwo
的构造函数再次实例化了 ClassOne
。这种情况反复发生,直到它溢出堆栈。
接下来,我们将看看当一个类作为该类的实例变量在同一个类中实例化时会发生什么。
如下一个示例所示, AccountHolder
将自身实例化为实例变量 JointaCountHolder
:
public class AccountHolder { private String firstName; private String lastName; AccountHolder jointAccountHolder = new AccountHolder(); }
当 AccountHolder
类实例化时,由于构造函数的递归调用,会引发StackOverflowError错误,如本测试中所示:
public class AccountHolderManualTest { @Test(expected = StackOverflowError.class) public void whenInstanciatingAccountHolder_thenThrowsException() { AccountHolder holder = new AccountHolder(); } }
当遇到StackOverflowError堆栈溢出错误时,最好的做法是仔细检查堆栈跟踪,以识别行号的重复模式。这将使我们能够定位具有问题递归的代码。
让我们研究一下由我们前面看到的代码示例引起的几个堆栈跟踪。
如果忽略预期的异常声明,则此堆栈跟踪由 InfiniteCursionWithTerminationConditionManualTest
生成:
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)
在这里,可以看到第5行重复。这就是进行递归调用的地方。现在只需要检查代码,看看递归是否以正确的方式完成。
下面是我们通过执行 CyclicDependancyManualTest
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)
-1
an calculateFacttorial()</ übergeben wird code> Methode, dann wird die Beendigungsbedingung nie erfüllt, was zu einer nicht abschließenden/unendlichen Rekursion führt: 🎜rrreee🎜 Diese Testreihe demonstriert dieses Szenario: 🎜rrreee🎜In diesem speziellen Fall, wenn die Beendigungsbedingung einfach ausgedrückt wird als: 🎜rrreee 🎜 Der folgende Test zeigt diese Situation in der Praxis: 🎜rrreee🎜 Schauen wir uns nun ein Szenario an, in dem ein StackOverflowError-Fehler aufgrund einer zirkulären Beziehung zwischen Klassen auftritt. Betrachten wir <code>ClassOne
und ClassTwo
, die sich gegenseitig in ihren Konstruktoren instanziieren und so eine zirkuläre Beziehung erzeugen: 🎜rrreeerrreee🎜 Nehmen wir nun an, dass wir versuchen, ClassOne zu instanziieren, wie gezeigt in diesem Test: 🎜rrreee🎜Dies führt letztendlich zu einem StackOverflowError, weil der Konstruktor von ClassOne
ClassTwo
instanziiert, während der Konstruktor von ClassTwo
ClassOne
noch einmal. Dies geschieht wiederholt, bis der Stapel überläuft. 🎜🎜Als nächstes schauen wir uns an, was passiert, wenn eine Klasse in derselben Klasse wie eine Instanzvariable dieser Klasse instanziiert wird. 🎜🎜Wie im nächsten Beispiel gezeigt, instanziiert sich AccountHolder
selbst als Instanzvariable JointaCountHolder
: 🎜rrreee🎜Wenn die Klasse AccountHolder
instanziiert wird, da Rekursive Aufrufe des Konstruktors, die einen StackOverflowError auslösen, wie in diesem Test gezeigt: 🎜rrreee🎜StackOverflowError auflösen🎜🎜Wenn ein StackOverflowError auftritt, besteht die beste Vorgehensweise darin, den Stack-Trace sorgfältig zu untersuchen, um Duplikate des Zeilennummernmodells zu identifizieren. Dadurch können wir Code mit problematischer Rekursion lokalisieren. 🎜🎜Lassen Sie uns einige Stapelspuren untersuchen, die durch das Codebeispiel verursacht werden, das wir zuvor gesehen haben. 🎜🎜Wenn die erwartete Ausnahmedeklaration ignoriert wird, wird dieser Stack-Trace von InfiniteCursionWithTerminationConditionManualTest
generiert: 🎜rrreee🎜Hier können Sie sehen, dass Zeile 5 wiederholt wird. Hier werden die rekursiven Aufrufe durchgeführt. Jetzt müssen Sie nur noch den Code überprüfen, um festzustellen, ob die Rekursion korrekt erfolgt. 🎜🎜Hier ist der Stack-Trace, den wir durch die Ausführung von CyclicDependancyManualTest
erhalten (wiederum wird keine Ausnahme erwartet): 🎜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)
该堆栈跟踪显示了在循环关系中的两个类中导致问题的行号。ClassTwo的第9行和ClassOne的第9行指向构造函数中试图实例化另一个类的位置。
彻底检查代码后,如果以下任何一项(或任何其他代码逻辑错误)都不是错误的原因:
错误实现的递归(即没有终止条件)
类之间的循环依赖关系
在同一个类中实例化一个类作为该类的实例变量
尝试增加堆栈大小是个好主意。根据安装的JVM,默认堆栈大小可能会有所不同。
-Xss
标志可以用于从项目的配置或命令行增加堆栈的大小。
Das obige ist der detaillierte Inhalt vonSo lösen Sie das StackOverflowError-Fehlerproblem in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!