什么是StackOverflowError,是什么原因导致的,我应该如何处理它们?
当前回答
堆栈溢出通常是由于嵌套函数调用太深(在使用递归时尤其容易,即函数调用自身)或在堆栈上分配大量内存而使用堆更合适。
其他回答
堆栈溢出的意思就是:堆栈溢出。通常在程序中有一个堆栈,它包含局部作用域变量和例程执行结束时返回的地址。该堆栈往往是内存中的某个固定内存范围,因此它可以包含多少值是有限的。
如果堆栈是空的,你不能弹出,如果你这样做,你会得到堆栈溢出错误。
如果堆栈满了,你就不能推,如果你推了,你就会得到堆栈溢出错误。
当你在堆栈中分配了太多内存时,就会出现堆栈溢出。例如,在前面提到的递归中。
有些实现优化了某些形式的递归。特别是尾递归。尾递归例程是一种例程,其中递归调用作为例程所做的最后一件事出现。这样的例行调用被简化为跳转。
有些实现甚至实现了自己的递归堆栈,因此它们允许递归继续进行,直到系统内存耗尽。
你可以尝试的最简单的事情就是如果可以的话增加你的堆栈大小。如果不能这样做,那么第二种方法是查看是否有什么东西明显导致堆栈溢出。试着在调用前后打印一些东西到例程中。这可以帮助您找到失败的例程。
这里有一个例子
public static void main(String[] args) {
System.out.println(add5(1));
}
public static int add5(int a) {
return add5(a) + 5;
}
一个StackOverflowError基本上是当你试图做一些事情,最有可能调用自己,并一直到无穷大(或直到它给出一个StackOverflowError)。
Add5 (a)将调用自身,然后再次调用自身,依此类推。
这个问题的很多答案都是好的。但是,我想采取稍微不同的方法,并对内存的工作原理和(简化的)可视化更好地理解StackOverflow错误给出一些更深入的见解。这种理解不仅适用于Java,而且适用于所有进程。
在现代系统中,所有新进程都有自己的虚拟地址空间(VAS)。从本质上讲,VAS是操作系统在物理内存之上提供的抽象层,目的是确保进程之间不相互干扰内存。内核的任务是将虚拟地址映射到实际的物理地址。
VAS可以分为几个部分:
为了让CPU知道它应该做什么,机器指令必须加载到内存中。这通常被称为代码或文本段,具有静态大小。
在此之上,可以找到数据段和堆。数据段大小固定,包含全局变量或静态变量。 当程序遇到特殊情况时,它可能需要额外分配数据,这就是堆发挥作用的地方,因此它能够动态地增长大小。
堆栈位于虚拟地址空间的另一侧,并使用后进先出(LIFO)数据结构跟踪所有函数调用。与堆类似,程序在运行时可能需要额外的空间来跟踪正在调用的新函数调用。由于堆栈位于VAS的另一侧,它正向相反的方向增长,即朝着堆的方向增长。
博士TL;
这就是StackOverflow错误发挥作用的地方。
由于堆栈向下增长(朝向堆),可能会发生在某个时间点它不能继续增长,因为它会与堆地址空间重叠。一旦发生这种情况,就会发生StackOverflow错误。
发生这种情况最常见的原因是由于程序中的一个错误导致递归调用不能正确终止。
请注意,在某些系统上,VAS的行为可能略有不同,甚至可以分为更多的部分,但是,这种一般理解适用于所有UNIX系统。
一个简单的Java示例,由于错误的递归调用导致Java .lang. stackoverflowerror:
class Human {
Human(){
new Animal();
}
}
class Animal extends Human {
Animal(){
super();
}
}
public class Test01 {
public static void main(String[] args) {
new Animal();
}
}
参数和局部变量分配在堆栈上(对于引用类型,对象位于堆上,堆栈中的变量引用堆上的对象)。堆栈通常位于地址空间的上端,当它被用完时,它会朝向地址空间的底部(即朝向零)。
你的进程也有一个堆,它位于你的进程的底部。当您分配内存时,这个堆可以向地址空间的顶端增长。正如您所看到的,堆有可能与堆栈“碰撞”(有点像构造板块!!)。
导致堆栈溢出的常见原因是错误的递归调用。通常,这是当递归函数没有正确的终止条件时引起的,因此它最终永远调用自己。或者当终止条件良好时,可能是由于在实现终止条件之前需要太多的递归调用而导致的。
但是,使用GUI编程,可以生成间接递归。例如,你的应用程序可能正在处理paint消息,在处理它们的同时,它可能会调用一个函数,导致系统发送另一个paint消息。这里您没有显式地调用自己,但是OS/VM已经为您完成了。
To deal with them, you'll need to examine your code. If you've got functions that call themselves then check that you've got a terminating condition. If you have, then check that when calling the function you have at least modified one of the arguments, otherwise there'll be no visible change for the recursively called function and the terminating condition is useless. Also mind that your stack space can run out of memory before reaching a valid terminating condition, thus make sure your method can handle input values requiring more recursive calls.
如果没有明显的递归函数,则检查是否调用了间接导致函数被调用的库函数(如上面的隐式情况)。
推荐文章
- javax.transaction.Transactional vs . org.springframework.transaction.annotation.Transactional
- Java 8接口方法中不允许“同步”的原因是什么?
- 如何找到Java堆大小和内存使用(Linux)?
- 使用Enum实现单例(Java)
- RabbitMQ与通道和连接之间的关系
- buildSessionFactory()配置方法在Hibernate中已弃用?
- Spring MVC -如何获得所有的请求参数在一个地图在Spring控制器?
- 如何在Java中按两个字段排序?
- 文件之间的差异。路径中的分隔符和斜杠
- 为什么不使用异常作为常规的控制流呢?
- 在方法参数中使用NotNull注释
- Spring MVC中处理可选参数的@RequestParam
- Tomcat:如何查找正在运行的Tomcat版本?
- “java”、“javaw”和“javaws”之间有什么区别?
- 将Date对象转换为日历对象