Java中的StackOverflowError错误( 二 )

下面的测试在实践中显示了这种情况:
public class RecursionWithCorrectTerminationConditionManualTest {@Testpublic 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 = https://www.isolves.com/it/cxkf/yy/JAVA/2022-07-14/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 = https://www.isolves.com/it/cxkf/yy/JAVA/2022-07-14/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.StackOverflowErrorat 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 标志可以用于从项目的配置或命令行增加堆栈的大小 。


推荐阅读