首页 文章

如何获得StackOverflowError的完整堆栈

提问于
浏览
27

When observing a StackOverflowError how to retrieve the full call stack?

考虑这个简单的例子:

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]

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

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

背景是我得到了一个StackOverflowError(但是当增加堆栈大小时会发生这种情况)并且很难发现代码中的问题 . 我只从 java.util.regex.Pattern 获得多行,但不知道代码所谓的信息 . 应用程序太复杂,无法在每次调用 Pattern 时设置断点 .

5 回答

  • 1

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

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

    -XX:MaxJavaStackTraceDepth=1000000
    

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

    This list of non-standard JVM options提供更多详细信息:

    最大 . 没有 . Java异常的堆栈跟踪中的行(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.<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行,这不是完整的堆栈跟踪,但可能是它最有用的部分 .

  • 0

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

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

    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 (足够高以至于不能在"good"情况下触发并且足够低以至于无法到达) . 当然这会降低您的代码速度,并且只应该用于调试问题 .

    更新:我似乎错过了 Pattern 中出现的问题,这使问题变得复杂 . 但是,您可以在堆栈跟踪中的一个 Pattern 方法上使用条件方法断点,条件如下(实际值可能需要调整):

    Thread.currentThread().getStackTrace().length > 300
    

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

  • 35

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

    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);
            }
    
    
        }
    }
    
  • 4

    我会尝试插入一些东西来装饰类似于ExceptionUtils的堆栈跟踪输出,以便对同一个Class或Package重复调用 .

  • 0

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

相关问题