2011-03-02 100 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上的線程轉儲,通過在其堆棧結束前打印出有問題的線程的整個堆棧,可以爲我們提供關於調用者的詳細信息。

相關問題