您好,登錄后才能下訂單哦!
這篇文章主要介紹了Java中的StackOverflowError錯(cuò)誤問(wèn)題怎么解決的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Java中的StackOverflowError錯(cuò)誤問(wèn)題怎么解決文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。
StackOverflowError可能會(huì)讓Java開(kāi)發(fā)人員感到惱火,因?yàn)樗俏覀兛赡苡龅降淖畛R?jiàn)的運(yùn)行時(shí)錯(cuò)誤之一。 在本文中,我們將通過(guò)查看各種代碼示例以及如何處理它來(lái)了解此錯(cuò)誤是如何發(fā)生的。 Stack Frames和StackOverflowerError的發(fā)生方式 讓我們從基礎(chǔ)開(kāi)始。調(diào)用方法時(shí),將在調(diào)用堆棧上創(chuàng)建新的堆棧幀(stack frame)。該堆棧框架包含被調(diào)用方法的參數(shù)、其局部變。
StackOverflowError 可能會(huì)讓Java開(kāi)發(fā)人員感到惱火,因?yàn)樗俏覀兛赡苡龅降淖畛R?jiàn)的運(yùn)行時(shí)錯(cuò)誤之一。
在本文中,我們將通過(guò)查看各種代碼示例以及如何處理它來(lái)了解此錯(cuò)誤是如何發(fā)生的。
讓我們從基礎(chǔ)開(kāi)始。調(diào)用方法時(shí),將在調(diào)用堆棧上創(chuàng)建新的堆棧幀(stack frame)。該堆??蚣馨徽{(diào)用方法的參數(shù)、其局部變量和方法的返回地址,即在被調(diào)用方法返回后應(yīng)繼續(xù)執(zhí)行方法的點(diǎn)。
堆棧幀的創(chuàng)建將繼續(xù),直到到達(dá)嵌套方法中的方法調(diào)用結(jié)束。
在此過(guò)程中,如果JVM遇到?jīng)]有空間創(chuàng)建新堆棧幀的情況,它將拋出 StackOverflower
錯(cuò)誤。
JVM遇到這種情況的最常見(jiàn)原因是未終止/無(wú)限遞歸——StackOverflowerr的Javadoc描述提到,錯(cuò)誤是由于特定代碼段中的遞歸太深而引發(fā)的。
然而,遞歸并不是導(dǎo)致此錯(cuò)誤的唯一原因。在應(yīng)用程序不斷從方法內(nèi)調(diào)用方法直到堆棧耗盡的情況下,也可能發(fā)生這種情況。這是一種罕見(jiàn)的情況,因?yàn)闆](méi)有開(kāi)發(fā)人員會(huì)故意遵循糟糕的編碼實(shí)踐。另一個(gè)罕見(jiàn)的原因是方法中有大量局部變量。
當(dāng)應(yīng)用程序設(shè)計(jì)為類(lèi)之間具有循環(huán)關(guān)系時(shí),也可以?huà)伋鯯tackOverflowError。在這種情況下,會(huì)重復(fù)調(diào)用彼此的構(gòu)造函數(shù),從而引發(fā)此錯(cuò)誤。這也可以被視為遞歸的一種形式。
另一個(gè)引起此錯(cuò)誤的有趣場(chǎng)景是,如果一個(gè)類(lèi)在同一個(gè)類(lèi)中作為該類(lèi)的實(shí)例變量實(shí)例化。這將導(dǎo)致一次又一次(遞歸)調(diào)用同一類(lèi)的構(gòu)造函數(shù),最終導(dǎo)致堆棧溢出錯(cuò)誤。
在下面所示的示例中,由于意外遞歸,開(kāi)發(fā)人員忘記為遞歸行為指定終止條件,將拋出StackOverflowError錯(cuò)誤:
public class UnintendedInfiniteRecursion { public int calculateFactorial(int number) { return number * calculateFactorial(number - 1); } }
在這里,對(duì)于傳遞到方法中的任何值,在任何情況下都會(huì)引發(fā)錯(cuò)誤:
public class UnintendedInfiniteRecursionManualTest { @Test(expected = <a href="https://javakk.com/tag/stackoverflowerror" rel="external nofollow" rel="external nofollow" title="查看更多關(guān)于 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); } }
但是,在下一個(gè)示例中,指定了終止條件,但如果將值 -1
傳遞給 calculateFactorial()
方法,則永遠(yuǎn)不會(huì)滿(mǎn)足終止條件,這會(huì)導(dǎo)致未終止/無(wú)限遞歸:
public class InfiniteRecursionWithTerminationCondition { public int calculateFactorial(int number) { return number == 1 ? 1 : number * calculateFactorial(number - 1); } }
這組測(cè)試演示了此場(chǎng)景:
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); } }
在這種特殊情況下,如果將終止條件簡(jiǎn)單地表示為:
public class RecursionWithCorrectTerminationCondition { public int calculateFactorial(int number) { return number <= 1 ? 1 : number * calculateFactorial(number - 1); } }
下面的測(cè)試在實(shí)踐中顯示了這種情況:
public class RecursionWithCorrectTerminationConditionManualTest { @Test public void givenNegativeInt_whenCalcFact_thenCorrectlyCalc() { int numToCalcFactorial = -1; RecursionWithCorrectTerminationCondition rctc = new RecursionWithCorrectTerminationCondition(); assertEquals(1, rctc.calculateFactorial(numToCalcFactorial)); } }
現(xiàn)在讓我們來(lái)看一個(gè)場(chǎng)景,其中StackOverflowError錯(cuò)誤是由于類(lèi)之間的循環(huán)關(guān)系而發(fā)生的。讓我們考慮 ClassOne
和 ClassTwo
,它們?cè)谄錁?gòu)造函數(shù)中相互實(shí)例化,從而產(chǎn)生循環(huán)關(guān)系:
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; } }
現(xiàn)在讓我們假設(shè)我們嘗試實(shí)例化ClassOne,如本測(cè)試中所示:
public class CyclicDependancyManualTest { @Test(expected = StackOverflowError.class) public void whenInstanciatingClassOne_thenThrowsException() { ClassOne obj = new ClassOne(); } }
這最終導(dǎo)致了StackOverflowError錯(cuò)誤,因?yàn)?nbsp;ClassOne
的構(gòu)造函數(shù)實(shí)例化了 ClassTwo
,而 ClassTwo
的構(gòu)造函數(shù)再次實(shí)例化了 ClassOne
。這種情況反復(fù)發(fā)生,直到它溢出堆棧。
接下來(lái),我們將看看當(dāng)一個(gè)類(lèi)作為該類(lèi)的實(shí)例變量在同一個(gè)類(lèi)中實(shí)例化時(shí)會(huì)發(fā)生什么。
如下一個(gè)示例所示, AccountHolder
將自身實(shí)例化為實(shí)例變量 JointaCountHolder
:
public class AccountHolder { private String firstName; private String lastName; AccountHolder jointAccountHolder = new AccountHolder(); }
當(dāng) AccountHolder
類(lèi)實(shí)例化時(shí),由于構(gòu)造函數(shù)的遞歸調(diào)用,會(huì)引發(fā)StackOverflowError錯(cuò)誤,如本測(cè)試中所示:
public class AccountHolderManualTest { @Test(expected = StackOverflowError.class) public void whenInstanciatingAccountHolder_thenThrowsException() { AccountHolder holder = new AccountHolder(); } }
當(dāng)遇到StackOverflowError堆棧溢出錯(cuò)誤時(shí),最好的做法是仔細(xì)檢查堆棧跟蹤,以識(shí)別行號(hào)的重復(fù)模式。這將使我們能夠定位具有問(wèn)題遞歸的代碼。
讓我們研究一下由我們前面看到的代碼示例引起的幾個(gè)堆棧跟蹤。
如果忽略預(yù)期的異常聲明,則此堆棧跟蹤由 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ù)。這就是進(jìn)行遞歸調(diào)用的地方?,F(xiàn)在只需要檢查代碼,看看遞歸是否以正確的方式完成。
下面是我們通過(guò)執(zhí)行 CyclicDependancyManualTest
(同樣,沒(méi)有預(yù)期的異常)獲得的堆棧跟蹤:
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)
該堆棧跟蹤顯示了在循環(huán)關(guān)系中的兩個(gè)類(lèi)中導(dǎo)致問(wèn)題的行號(hào)。ClassTwo的第9行和ClassOne的第9行指向構(gòu)造函數(shù)中試圖實(shí)例化另一個(gè)類(lèi)的位置。
徹底檢查代碼后,如果以下任何一項(xiàng)(或任何其他代碼邏輯錯(cuò)誤)都不是錯(cuò)誤的原因:
錯(cuò)誤實(shí)現(xiàn)的遞歸(即沒(méi)有終止條件)
類(lèi)之間的循環(huán)依賴(lài)關(guān)系
在同一個(gè)類(lèi)中實(shí)例化一個(gè)類(lèi)作為該類(lèi)的實(shí)例變量
嘗試增加堆棧大小是個(gè)好主意。根據(jù)安裝的JVM,默認(rèn)堆棧大小可能會(huì)有所不同。
-Xss
標(biāo)志可以用于從項(xiàng)目的配置或命令行增加堆棧的大小。
關(guān)于“Java中的StackOverflowError錯(cuò)誤問(wèn)題怎么解決”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Java中的StackOverflowError錯(cuò)誤問(wèn)題怎么解決”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。