如何获得StackOverflowError的完整堆栈

观察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.(Overflow.java:11) [last line repeated many times] 

但我无法在堆栈跟踪中看到main和方法。 我的猜测是因为溢出,堆栈中的最新条目取代了最旧的条目(?)。

现在,如何在输出中获取amain堆栈条目?

背景是我得到一个StackOverflowError(但这不是无限递归,因为它在增加堆栈大小时不会发生)并且很难在代码中发现问题。 我只从java.util.regex.Pattern获取多行,但不知道代码调用的那些信息。 应用程序太复杂,无法在每次调用Pattern设置断点。

JVM具有1024个条目的人为限制,您可以在exception或错误的堆栈跟踪中拥有这些条目,可能在发生内存时节省内存(因为VM必须分配内存来存储堆栈跟踪)。

幸运的是,有一个标志允许增加此限制。 只需使用以下参数运行程序:

 -XX:MaxJavaStackTraceDepth=1000000 

这将打印多达100万个堆栈跟踪条目,这应该绰绰有余。 也可以将此值设置为-1以将条目数设置为无限制。

此非标准JVM选项列表提供了更多详细信息:

最大。 没有。 Javaexception的堆栈跟踪中的行(0表示全部)。 对于Java> 1.6,值0实际上意味着0.值-1或必须指定任何负数来打印所有堆栈(在Windows上使用1.6.0_22,1.7.0进行测试)。 对于Java <= 1.5,值0表示所有内容,JVM在负数上阻塞(在Windows上使用1.5.0_22进行测试)。

使用此标志运行问题的示例将得到以下结果:

 Exception in thread "main" java.lang.StackOverflowError at Overflow.(Overflow.java:3) at Overflow.(Overflow.java:4) at Overflow.(Overflow.java:4) at Overflow.(Overflow.java:4) (more than ten thousand lines later:) at Overflow.(Overflow.java:4) at Overflow.(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行,这不是完整的堆栈跟踪,但可能是它最有用的部分。

据我所知,不可能得到完整的堆栈跟踪(但是,我真的不知道为什么)。

但是,您可以采取哪些措施来跟踪问题,就是在受影响的代码中手动检查堆栈深度,如下所示:

 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 

这样,当您点击断点时,您可以在堆栈底部找到自己的代码。

如果您的堆栈不足,请考虑创建一个具有足够堆栈的专用线程,尤其是用于运行请求。 示例代码如下。

 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 queue = new SynchronousQueue(); 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 % c = new Callable(){ @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); } } } 

我尝试插入一些东西来装饰类似于ExceptionUtils的堆栈跟踪输出,以将重复调用分组到同一个Class或Package。

我会在重现问题时触发手动线程转储。 很可能只有一段时间后才会抛出stackoverflow。 因此,我们可以快速触发jvm上的Thread转储,它将通过在堆栈飞越之前打印整个有问题的线程堆栈来为我们提供有关调用者的详细信息。