文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java中的StackOverflowError错误问题怎么解决

2023-07-02 16:39

关注

这篇文章主要介绍了Java中的StackOverflowError错误问题怎么解决的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Java中的StackOverflowError错误问题怎么解决文章都会有所收获,下面我们一起来看看吧。

StackOverflowError简介

StackOverflowError可能会让Java开发人员感到恼火,因为它是我们可能遇到的最常见的运行时错误之一。 在本文中,我们将通过查看各种代码示例以及如何处理它来了解此错误是如何发生的。 Stack Frames和StackOverflowerError的发生方式 让我们从基础开始。调用方法时,将在调用堆栈上创建新的堆栈帧(stack frame)。该堆栈框架包含被调用方法的参数、其局部变。

StackOverflowError 可能会让Java开发人员感到恼火,因为它是我们可能遇到的最常见的运行时错误之一。

在本文中,我们将通过查看各种代码示例以及如何处理它来了解此错误是如何发生的。

Stack Frames和StackOverflowerError的发生方式

让我们从基础开始。调用方法时,将在调用堆栈上创建新的堆栈帧(stack frame)。该堆栈框架包含被调用方法的参数、其局部变量和方法的返回地址,即在被调用方法返回后应继续执行方法的点。

堆栈帧的创建将继续,直到到达嵌套方法中的方法调用结束。

在此过程中,如果JVM遇到没有空间创建新堆栈帧的情况,它将抛出 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 (同样,没有预期的异常)获得的堆栈跟踪:

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错误问题怎么解决”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯