观察StackOverflowError时如何检索完整的调用堆栈?
考虑这个简单的例子:
public class Overflow {
public Overflow() {
new Overflow();
}
public static void a() {
new Overflow();
}
public static void main(String[] argv) {
a();
}
}
现在报告的错误是:
Exception in thread "main" java.lang.StackOverflowError
at Overflow.<init>(Overflow.java:11)
[last line repeated many times]
但我在堆栈跟踪中看不到main
和a
方法。我的猜测是因为溢出,堆栈中的最新条目取代了最旧的条目(?)。
现在,如何在输出中获取a
和main
堆栈条目?
背景是我得到了一个StackOverflowError(但这不是无限递归,因为它在增加堆栈大小时不会发生)并且很难在代码中发现问题。我只从java.util.regex.Pattern
获得多行,但不知道代码所称的那些信息。该应用程序太复杂,无法在每次调用Pattern
时设置断点。
答案 0 :(得分:37)
JVM在异常或错误的堆栈跟踪中可以拥有1024个条目的人为限制,可能是为了在内存发生时节省内存(因为VM必须分配内存来存储堆栈跟踪)。
幸运的是,有一个标志允许增加此限制。只需使用以下参数运行程序:
-XX:MaxJavaStackTraceDepth=1000000
这将打印多达100万个堆栈跟踪条目,这应该绰绰有余。也可以将此值设置为-1
,以将条目数设置为无限制。
This list of non-standard JVM options提供了更多详细信息:
最大。没有。堆栈跟踪中的行为Java异常(0表示 所有)。使用Java&gt; 1.6,值0实际上意味着0.值-1或任何 必须指定负数才能打印所有堆栈(使用 Windows上的1.6.0_22,1.7.0)。使用Java&lt; = 1.5,值0表示一切,JVM在负数上阻塞(在1.5.0_22上测试 窗口)。
使用此标志运行问题样本会得到以下结果:
Exception in thread "main" java.lang.StackOverflowError
at Overflow.<init>(Overflow.java:3)
at Overflow.<init>(Overflow.java:4)
at Overflow.<init>(Overflow.java:4)
at Overflow.<init>(Overflow.java:4)
(more than ten thousand lines later:)
at Overflow.<init>(Overflow.java:4)
at Overflow.<init>(Overflow.java:4)
at Overflow.a(Overflow.java:7)
at Overflow.main(Overflow.java:10)
这样,即使实际堆栈跟踪长度超过1024行,您也可以找到引发错误的代码的原始调用者。
如果您不能使用该选项,还有另一种方法,如果您处于这样的递归函数中,并且您可以修改它。如果添加以下try-catch:
public Overflow() {
try {
new Overflow();
}
catch(StackOverflowError e) {
StackTraceElement[] stackTrace = e.getStackTrace();
// if the stack trace length is at the limit , throw a new StackOverflowError, which will have one entry less in it.
if (stackTrace.length == 1024) {
throw new StackOverflowError();
}
throw e; // if it is small enough, just rethrow it.
}
}
基本上,这将创建并抛出一个新的StackOverflowError
,丢弃最后一个条目,因为每个条目将比前一个条目向上发送一级(这可能需要几秒钟,因为所有这些错误都必须被创造)。当堆栈跟踪将减少到1023个元素时,它只是被重新抛出。
最终这将在堆栈跟踪的底部打印1023行,这不是完整的堆栈跟踪,但可能是它最有用的部分。
答案 1 :(得分:4)
据我所知,不可能获得完整的堆栈跟踪(但是,我真的不知道为什么)。
但是,您可以采取哪些措施来追踪问题,就是在受影响的代码中手动检查堆栈深度,如下所示:
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (trace.length > SOME_VALUE) {
// trigger some diagnostic action, print a stack trace or have a breakpoint here
}
需要通过实验找到 SOME_VALUE
(足够高以至于不能在“好”情况下触发并且足够低以至于无法达到)。当然这会降低你的代码速度,只能用于调试问题。
更新:我似乎错过了Pattern
中出现的问题,这使问题变得复杂。但是,您可以在堆栈跟踪中的一个Pattern
方法上使用条件方法断点,条件如下(实际值可能需要调整):
Thread.currentThread().getStackTrace().length > 300
这样,当您点击断点时,您可以在堆栈底部找到自己的代码。
答案 2 :(得分:1)
如果您的堆栈不足,请考虑创建一个具有足够堆栈的专用线程,尤其是用于运行请求。示例代码如下。
package t1;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
public class RegExpRunner {
ExecutorService svc;
public RegExpRunner(long stackSize){
init(stackSize);
}
void init(long stackSize){
final SynchronousQueue<Runnable> queue = new SynchronousQueue<Runnable>();
svc = new ThreadPoolExecutor(1, 2, 60, TimeUnit.SECONDS, queue, createThreadFactory(stackSize), new RejectedExecutionHandler(){//wait if there is a concurrent compile and no available threads
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try{
queue.put(r);
}catch(InterruptedException _ie){
Thread.currentThread().interrupt();
throw new IllegalStateException(_ie);
}
}
});
}
private ThreadFactory createThreadFactory(final long stackSize) {
return new ThreadFactory(){
final ThreadGroup g = Thread.currentThread().getThreadGroup();
private final AtomicLong counter= new AtomicLong();
{
//take care of contextClassLoader and AccessControlContext
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(g, r, composeName(r), stackSize);
return t;
}
protected String composeName(Runnable r) {
return String.format("Regexp dedicated compiler: %d @ %tF %<tT ", counter.incrementAndGet(), System.currentTimeMillis());
}
};
};
public Pattern compile(final String regex){//add flags if you need 'em
Callable<Pattern> c = new Callable<Pattern>(){
@Override
public Pattern call() throws Exception {
return Pattern.compile(regex);
}
};
try{
Pattern p = svc.submit(c).get();
return p;
}catch(InterruptedException _ie){
Thread.currentThread().interrupt();
throw new IllegalStateException(_ie);
} catch(CancellationException _cancel){
throw new AssertionError(_cancel);//shan't happen
} catch(ExecutionException _exec){
Throwable t = _exec.getCause();
if (t instanceof RuntimeException) throw (RuntimeException) t;
if (t instanceof Error) throw (Error) t;
throw new IllegalStateException(t==null?_exec:t);
}
}
}
答案 3 :(得分:0)
我会尝试插入一些东西来装饰类似于ExceptionUtils的堆栈跟踪输出,以便对同一个Class或Package重复调用。
答案 4 :(得分:0)
我会在重现问题时触发手动线程转储。很可能只有一段时间后才会抛出stackoverflow。因此,我们可以在jvm上快速触发一个Thread转储,它将通过在堆栈飞越之前打印整个有问题的线程堆栈来为我们提供有关调用者的详细信息。