这个问题在这里已有答案:
我正在调查Java应用程序正在生成的StackOverflowError。堆栈跟踪看起来像这样(抱歉,我无法共享实际的生产跟踪):
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
at test.StackOverflowTest.foo(StackOverflowTest.java:24)
...
函数foo
本身在代码检查和单元测试中似乎是正确的,表明这是传递给foo
的数据的问题。
问题是堆栈大小大于异常中堆栈跟踪的限制。这意味着未显示堆栈跟踪的开始,使得进一步调查非常困难。
如何让Java显示堆栈跟踪的开头?
我相信Java可以配置为减少堆栈大小或增加异常限制。但是,我担心在生产中调整这些价值的其他影响。如果Java会使用堆栈中的前50个和最后50个调用,那将会更有帮助。
如果您可以编辑生产代码并在某处运行它;您可以修改代码,以便在特定的递归深度转储堆栈跟踪。这允许你“看到堆栈跟踪的底部”。
当然,您需要以兼容的方式修改生产代码(例如,不允许在foo
方法中添加“深度”参数 - 因为这会影响您的客户)。
见例如。以下代码;我们将递归深度存储在线程局部变量中。
package lang;
/**
* run with -Dmy.debug.dump.enabled=true
*/
public class StackOverflowTest {
public static void main(String[] args) {
try {
StackOverflowTest o = new StackOverflowTest();
o.foo();
} catch (StackOverflowError err) {
System.out.println("err: StackOverflowError");
}
}
private static ThreadLocal<Integer> recurseCount = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
};
};
private static final boolean DUMP_ENABLED;
static {
String sysprop = System.getProperty("my.debug.dump.enabled");
DUMP_ENABLED = sysprop!=null && "true".equals(sysprop);
}
// or set it via system properties
private static final int DUMP_ON_RECURSION_NUM=4;
class MyRecurseDump extends Exception {
public MyRecurseDump(String msg) {
super(msg);
}
private static final long serialVersionUID = 1L;
}
private void foo() {
try {
if (DUMP_ENABLED) {
recurseCount.set(recurseCount.get()+1);
if (recurseCount.get()==DUMP_ON_RECURSION_NUM) {
new MyRecurseDump("foo: reached num="+DUMP_ON_RECURSION_NUM+" recursion depth")
.printStackTrace(System.err);
}
}
// put foo code here
int x;
foo();
// end of foo code
//*********************************************
}
finally {
if (DUMP_ENABLED) {
recurseCount.set(recurseCount.get()-1);
}
}
}
}
使用java -Dmy.debug.dump.enabled=true lang.StackOverflowTest
运行它,输出是:
lang.StackOverflowTest$MyRecurseDump: foo: reached num=4 recursion depth
at lang.StackOverflowTest.foo(StackOverflowTest.java:44)
at lang.StackOverflowTest.foo(StackOverflowTest.java:53)
at lang.StackOverflowTest.foo(StackOverflowTest.java:53)
at lang.StackOverflowTest.foo(StackOverflowTest.java:53)
at lang.StackOverflowTest.main(StackOverflowTest.java:11)
err: StackOverflowError
它可以通过多种方式进行调整(在示例中更改递归深度);或者只在整个程序中执行单个转储(因为您可能有多个转储)。
您可能认为堆栈溢出错误就像本机程序中的缓冲区溢出异常,当存在写入尚未为缓冲区分配的内存的风险时,从而破坏其他一些内存位置。事实并非如此。
JVM具有为每个线程的每个堆栈分配的给定内存,并且如果尝试调用方法恰好填充此内存,则JVM会抛出错误。就像你试图在长度为N的数组的索引N处写一样。没有内存损坏可能发生。堆栈无法写入堆。
StackOverflowError是堆栈OutOfMemoryError对堆的唯一信号:它只是表示没有更多可用内存。