這篇文章主要介紹了Java中的StackOverflowError錯誤問題怎么解決的相關知識,內容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇Java中的StackOverflowError錯誤問題怎么解決文章都會有所收獲,下面我們一起來看看吧。
StackOverflowError可能會讓Java開發人員感到惱火,因為它是我們可能遇到的最常見的運行時錯誤之一。 在本文中,我們將通過查看各種代碼示例以及如何處理它來了解此錯誤是如何發生的。 Stack Frames和StackOverflowerError的發生方式 讓我們從基礎開始。調用方法時,將在調用堆棧上創建新的堆棧幀(stack frame)。該堆??蚣馨徽{用方法的參數、其局部變。
StackOverflowError 可能會讓Java開發人員感到惱火,因為它是我們可能遇到的最常見的運行時錯誤之一。
在本文中,我們將通過查看各種代碼示例以及如何處理它來了解此錯誤是如何發生的。
讓我們從基礎開始。調用方法時,將在調用堆棧上創建新的堆棧幀(stack frame)。該堆??蚣馨徽{用方法的參數、其局部變量和方法的返回地址,即在被調用方法返回后應繼續執行方法的點。
堆棧幀的創建將繼續,直到到達嵌套方法中的方法調用結束。
在此過程中,如果JVM遇到沒有空間創建新堆棧幀的情況,它將拋出 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行重復。這就是進行遞歸調用的地方?,F在只需要檢查代碼,看看遞歸是否以正確的方式完成。
下面是我們通過執行 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)
該堆棧跟蹤顯示了在循環關系中的兩個類中導致問題的行號。ClassTwo的第9行和ClassOne的第9行指向構造函數中試圖實例化另一個類的位置。
徹底檢查代碼后,如果以下任何一項(或任何其他代碼邏輯錯誤)都不是錯誤的原因:
錯誤實現的遞歸(即沒有終止條件)
類之間的循環依賴關系
在同一個類中實例化一個類作為該類的實例變量
嘗試增加堆棧大小是個好主意。根據安裝的JVM,默認堆棧大小可能會有所不同。
-Xss
標志可以用于從項目的配置或命令行增加堆棧的大小。
關于“Java中的StackOverflowError錯誤問題怎么解決”這篇文章的內容就介紹到這里,感謝各位的閱讀!相信大家對“Java中的StackOverflowError錯誤問題怎么解決”知識都有一定的了解,大家如果還想學習更多知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。