2011-03-02 103 views
23

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

但我不能看到堆栈跟踪maina方法。我的猜测是这是因为溢出,栈上最新的条目取代了最老的(?)。

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

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

+0

鉴于您可以增加堆栈大小并消失,您是否可以尝试减小堆栈大小并查看是否允许您查看更多堆栈跟踪?无法记住您如何控制JVM中的堆栈大小,以及是否允许将它设置得足够小,但它可能有助于诊断问题。 – 2011-03-02 10:10:20

+0

@Tom,堆栈大小由新的Thread(ThreadGroup组,Runnable target,String name,long stackSize)控制。 – bestsss 2011-03-02 10:26:37

+0

哦,好的。好主意:-( – 2011-03-02 10:26:59

回答

27

JVM有1024个条目的人为限制,你可以在一个异常或错误的堆栈跟踪,可能发生时以节省内存(因为虚拟机必须分配的内存来存储堆栈跟踪)。

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

-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行,这不是完整的堆栈跟踪,但可能是最有用的部分。

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 

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

+0

*据我所知,这是不可能得到完整的堆栈跟踪(但是,我真的不知道为什么)。 *一个明显的原因是你可能会耗尽内存,甚至试图收集它并重建它。堆栈跟踪并不是java本身所需要的(出于安全原因,这是安全的)。 300通常是非常低的深度(除非堆栈中有大量变量),但是收集堆栈跟踪是非空闲的。 – bestsss 2011-03-02 10:24:42

+0

谢谢。有条件的断点完成了这项工作! – 2011-03-02 10:55:43

0

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

+0

我认为这一点是由java报告的堆栈跟踪在底部(即main)之前停止,所以信息不会在那里报告。据推测,它可以达到所报告元素的最大容量,并且当发生堆栈溢出时,程序已超出该限制。有点奇怪的是,这个容量与发生堆栈溢出的深度不一样。 – 2011-03-02 10:08:50

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


    } 
} 
0

我会在重现问题时触发手动线程转储。大多数情况下,只有一段时间后才会抛出stackoverflow。因此,我们可以快速触发jvm上的线程转储,通过在其堆栈结束前打印出有问题的线程的整个堆栈,可以为我们提供关于调用者的详细信息。

相关问题