Heim >Java >javaLernprogramm >So lösen Sie das StackOverflowError-Fehlerproblem in Java

So lösen Sie das StackOverflowError-Fehlerproblem in Java

WBOY
WBOYnach vorne
2023-05-13 20:49:043335Durchsuche

Einführung in StackOverflowError

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.

Wie Stack Frames und StackOverflowerError auftreten

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。在这种情况下,会重复调用彼此的构造函数,从而引发此错误。这也可以被视为递归的一种形式。

另一个引起此错误的有趣场景是,如果一个类在同一个类中作为该类的实例变量实例化。这将导致一次又一次(递归)调用同一类的构造函数,最终导致堆栈溢出错误。

StackOverflowerError正在运行

在下面所示的示例中,由于意外递归,开发人员忘记为递归行为指定终止条件,将抛出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

当遇到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

Der häufigste Grund, warum JVM darauf stößt, ist eine nicht abgeschlossene/unendliche Rekursion. In der Javadoc-Beschreibung von StackOverflowerr wird erwähnt, dass der Fehler durch eine zu tiefe Rekursion in einem bestimmten Codeabschnitt verursacht wird. 🎜🎜 Rekursion ist jedoch nicht die einzige Ursache für diesen Fehler. Dies kann auch in Situationen passieren, in denen eine Anwendung so lange Methoden innerhalb einer Methode aufruft, bis der Stapel erschöpft ist. Dies ist eine seltene Situation, da kein Entwickler absichtlich schlechte Codierungspraktiken befolgen würde. Ein weiterer seltener Grund ist eine große Anzahl lokaler Variablen in der Methode. 🎜🎜StackOverflowError kann auch ausgelöst werden, wenn die Anwendung für zyklische Beziehungen zwischen Klassen ausgelegt ist. In diesem Fall werden die Konstruktoren des anderen wiederholt aufgerufen, was diesen Fehler verursacht. Dies kann auch als eine Form der Rekursion betrachtet werden. 🎜🎜Ein weiteres interessantes Szenario, das diesen Fehler verursacht, ist, wenn eine Klasse innerhalb derselben Klasse wie eine Instanzvariable dieser Klasse instanziiert wird. Dies führt dazu, dass der Konstruktor derselben Klasse immer wieder (rekursiv) aufgerufen wird, was schließlich zu einem Stapelüberlauffehler führt. 🎜🎜StackOverflowError in Aktion🎜🎜Im unten gezeigten Beispiel hat der Entwickler aufgrund einer unerwarteten Rekursion vergessen, eine Beendigungsbedingung für das rekursive Verhalten anzugeben. Es wird ein StackOverflowError-Fehler ausgegeben: 🎜
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)
🎜Hier gilt für jeden an die Methode übergebenen Wert , In jedem Fall wird ein Fehler ausgegeben: 🎜rrreee🎜 Im nächsten Beispiel wird jedoch die Abbruchbedingung angegeben, wenn jedoch der Wert -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!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen