什么是StackOverflowError,是什么原因导致的,我应该如何处理它们?
当前回答
StackOverflowError之于堆栈,就像OutOfMemoryError之于堆。
无界递归调用会导致堆栈空间被用完。
产生StackOverflowError的示例如下:
class StackOverflowDemo
{
public static void unboundedRecursiveCall() {
unboundedRecursiveCall();
}
public static void main(String[] args)
{
unboundedRecursiveCall();
}
}
如果对递归调用进行了限制,以防止内存中不完整调用的总和(以字节为单位)超过堆栈大小(以字节为单位),则可以避免StackOverflowError。
其他回答
下面是一个递归算法的例子,用于反转单链表。在笔记本电脑(规格为4gb内存,Intel Core i5 2.3 GHz CPU 64位,Windows 7)上,对于大小接近10,000的链表,该函数将遇到StackOverflow错误。
我的观点是,我们应该明智地使用递归,始终考虑到系统的规模。
通常递归可以转换为迭代程序,迭代程序的伸缩性更好。(本页底部给出了同一算法的一个迭代版本。它在9毫秒内反转大小为100万的单链表。)
private static LinkedListNode doReverseRecursively(LinkedListNode x, LinkedListNode first){
LinkedListNode second = first.next;
first.next = x;
if(second != null){
return doReverseRecursively(first, second);
}else{
return first;
}
}
public static LinkedListNode reverseRecursively(LinkedListNode head){
return doReverseRecursively(null, head);
}
同一算法的迭代版本:
public static LinkedListNode reverseIteratively(LinkedListNode head){
return doReverseIteratively(null, head);
}
private static LinkedListNode doReverseIteratively(LinkedListNode x, LinkedListNode first) {
while (first != null) {
LinkedListNode second = first.next;
first.next = x;
x = first;
if (second == null) {
break;
} else {
first = second;
}
}
return first;
}
public static LinkedListNode reverseIteratively(LinkedListNode head){
return doReverseIteratively(null, head);
}
在紧急情况下,以下情况将导致堆栈溢出错误。
public class Example3 {
public static void main(String[] args) {
main(new String[1]);
}
}
如您所说,您需要展示一些代码。: -)
堆栈溢出错误通常发生在函数调用nest太深的时候。请参阅Stack Overflow Code Golf线程,以了解如何发生这种情况(尽管在这个问题的情况下,答案会故意导致堆栈溢出)。
这是一个典型的java.lang.StackOverflowError…该方法递归地调用自身,在doubleValue()、floatValue()等中没有出口。
文件Rational.java
public class Rational extends Number implements Comparable<Rational> {
private int num;
private int denom;
public Rational(int num, int denom) {
this.num = num;
this.denom = denom;
}
public int compareTo(Rational r) {
if ((num / denom) - (r.num / r.denom) > 0) {
return +1;
} else if ((num / denom) - (r.num / r.denom) < 0) {
return -1;
}
return 0;
}
public Rational add(Rational r) {
return new Rational(num + r.num, denom + r.denom);
}
public Rational sub(Rational r) {
return new Rational(num - r.num, denom - r.denom);
}
public Rational mul(Rational r) {
return new Rational(num * r.num, denom * r.denom);
}
public Rational div(Rational r) {
return new Rational(num * r.denom, denom * r.num);
}
public int gcd(Rational r) {
int i = 1;
while (i != 0) {
i = denom % r.denom;
denom = r.denom;
r.denom = i;
}
return denom;
}
public String toString() {
String a = num + "/" + denom;
return a;
}
public double doubleValue() {
return (double) doubleValue();
}
public float floatValue() {
return (float) floatValue();
}
public int intValue() {
return (int) intValue();
}
public long longValue() {
return (long) longValue();
}
}
文件Main.java
public class Main {
public static void main(String[] args) {
Rational a = new Rational(2, 4);
Rational b = new Rational(2, 6);
System.out.println(a + " + " + b + " = " + a.add(b));
System.out.println(a + " - " + b + " = " + a.sub(b));
System.out.println(a + " * " + b + " = " + a.mul(b));
System.out.println(a + " / " + b + " = " + a.div(b));
Rational[] arr = {new Rational(7, 1), new Rational(6, 1),
new Rational(5, 1), new Rational(4, 1),
new Rational(3, 1), new Rational(2, 1),
new Rational(1, 1), new Rational(1, 2),
new Rational(1, 3), new Rational(1, 4),
new Rational(1, 5), new Rational(1, 6),
new Rational(1, 7), new Rational(1, 8),
new Rational(1, 9), new Rational(0, 1)};
selectSort(arr);
for (int i = 0; i < arr.length - 1; ++i) {
if (arr[i].compareTo(arr[i + 1]) > 0) {
System.exit(1);
}
}
Number n = new Rational(3, 2);
System.out.println(n.doubleValue());
System.out.println(n.floatValue());
System.out.println(n.intValue());
System.out.println(n.longValue());
}
public static <T extends Comparable<? super T>> void selectSort(T[] array) {
T temp;
int mini;
for (int i = 0; i < array.length - 1; ++i) {
mini = i;
for (int j = i + 1; j < array.length; ++j) {
if (array[j].compareTo(array[mini]) < 0) {
mini = j;
}
}
if (i != mini) {
temp = array[i];
array[i] = array[mini];
array[mini] = temp;
}
}
}
}
结果
2/4 + 2/6 = 4/10
Exception in thread "main" java.lang.StackOverflowError
2/4 - 2/6 = 0/-2
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 * 2/6 = 4/24
at com.xetrasu.Rational.doubleValue(Rational.java:64)
2/4 / 2/6 = 12/8
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
at com.xetrasu.Rational.doubleValue(Rational.java:64)
下面是OpenJDK 7中StackOverflowError的源代码。
堆栈有一个空间限制,这取决于操作系统。正常的大小是8mb(在Ubuntu (Linux)中,你可以用$ ulimit -u检查这个限制,在其他操作系统中也可以类似地检查)。任何程序都在运行时使用堆栈,但要完全了解何时使用堆栈,您需要检查汇编语言。例如,在x86_64中,堆栈用于:
在进行过程调用时保存返回地址 保存本地变量 保存特殊寄存器以便稍后恢复它们 向过程调用传递参数(大于6) 其他:随机未使用的堆栈基础,金丝雀值,填充,…等。
如果您不知道x86_64(一般情况下),您只需要知道您所使用的特定高级编程语言何时编译这些操作。例如在C语言中:
→函数调用 (2)→函数调用中的局部变量(包括main) (3)→函数调用中的局部变量(不是main) →函数调用 (5)→通常是一个函数调用,通常与堆栈溢出无关。
因此,在C语言中,只有局部变量和函数调用使用堆栈。造成堆栈溢出的两种(唯一的?)方法是:
在main或任何函数中声明过大的局部变量(int array[10000][10000];) 深度递归或无限递归(同时调用太多函数)。
要避免StackOverflowError,您可以:
检查局部变量是否太大(1mb数量级)→使用堆(malloc/calloc调用)或全局变量。 检查无限递归→你知道该怎么做…正确的! 检查常规的深度递归→最简单的方法是将实现更改为迭代。
还要注意全局变量、包含库等等……不要使用堆栈。
只有当上述方法不起作用时,才可以在特定的操作系统上将堆栈大小更改为最大值。例如Ubuntu: ulimit -s 32768 (32 MB)。(这从来都不是我的任何堆栈溢出错误的解决方案,但我也没有太多经验。)
我省略了C语言中的特殊和/或非标准情况(例如alloc()和类似的用法),因为如果你正在使用它们,你应该已经确切地知道你在做什么。
推荐文章
- 在流中使用Java 8 foreach循环移动到下一项
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 用Java计算两个日期之间的天数
- 如何配置slf4j-simple
- 在Jar文件中运行类
- 带参数的可运行?
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 我可以在Java中设置enum起始值吗?
- Java中的回调函数
- c#和Java中的泛型有什么不同?和模板在c++ ?
- 在Java中,流相对于循环的优势是什么?
- Jersey在未找到InjectionManagerFactory时停止工作
- 在Java流是peek真的只是调试?
- Recyclerview不调用onCreateViewHolder
- $destroy是否删除事件监听器?