建立和閱讀堆疊跟蹤

當建立異常物件時(即,當你建立異常物件時),Throwable 建構函式捕獲有關建立異常的上下文的資訊。稍後,此資訊可以以堆疊跟蹤的形式輸出,可用於幫助診斷導致異常的問題。

列印堆疊跟蹤

列印堆疊跟蹤只是呼叫 printStackTrace() 方法的問題。例如:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // This prints the stacktrace to standard output
    ex.printStackTrace();
}

不帶引數的 printStackTrace() 方法將列印到應用程式的標準輸出; 即目前的 System.out。還有 printStackTrace(PrintStream)printStackTrace(PrintWriter) 過載列印到指定的 StreamWriter

筆記:

  1. 堆疊跟蹤不包含異常本身的詳細資訊。你可以使用 toString() 方法獲取這些細節; 例如

       // Print exception and stacktrace
       System.out.println(ex);
       ex.printStackTrace();
    
  2. 應謹慎使用 Stacktrace 列印; 看陷阱 - 過多或不適當的堆疊跟蹤 。通常最好使用日誌記錄框架,並傳遞要記錄的異常物件。

瞭解堆疊跟蹤

考慮以下由兩個檔案中的兩個類組成的簡單程式。 (為了便於說明,我們已經顯示了檔名和新增的行號。)

File: "Main.java"
1   public class Main {
2       public static void main(String[] args) {
3           new Test().foo();
4       }
5   }

File: "Test.java"
1   class Test {
2       public void foo() {
3           bar();
4       }
5   
6       public int bar() {
7           int a = 1;
8           int b = 0;
9           return a / b;
10      }

編譯並執行這些檔案時,我們將獲得以下輸出。

Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Test.bar(Test.java:9)
        at Test.foo(Test.java:3)
        at Main.main(Main.java:3)

讓我們一次閱讀這一行,找出它告訴我們的內容。

第 1 行告訴我們,由於未捕獲的異常,名為 main 的執行緒已終止。例外的全名是 java.lang.ArithmeticException,異常訊息是“/ by zero”。

如果我們查詢此異常的 javadoc,它會說:

發生異常算術條件時丟擲。例如,整數除以零會丟擲此類的例項。

實際上,訊息“/ by zero”強烈暗示異常的原因是某些程式碼試圖將某些內容除以零。但是什麼?

其餘 3 行是堆疊跟蹤。每一行代表呼叫堆疊上的方法(或建構函式)呼叫,每一行都告訴我們三件事:

  • 正在執行的類和方法的名稱,
  • 原始碼檔名,
  • 正在執行的語句的原始碼行號

堆疊跟蹤的這些行與頂部的當前呼叫的框架一起列出。上面示例中的頂部框架位於 Test.bar 方法中,位於 Test.java 檔案的第 9 行。這是以下行:

    return a / b;

如果我們在檔案的前面看幾行來初始化 b,那麼顯然 b 的值為零。毫無疑問,我們可以說這是例外的原因。

如果我們需要更進一步,我們可以從堆疊跟蹤中看到 bar() 是從 Test.java 第 3 行的 foo() 呼叫的,而 foo() 又是從 Main.main() 呼叫的。

注意:堆疊幀中的類和方法名稱是類和方法的內部名稱。你需要識別以下不尋常的情況:

  • 巢狀或內部類看起來像“OuterClass $ InnerClass”。
  • 匿名內部類看起來像“OuterClass $ 1”,“OuterClass $ 2”等。
  • 當正在執行建構函式,例項欄位初始值設定項或例項初始化程式塊中的程式碼時,方法名稱將為“”。
  • 當正在執行靜態欄位初始化程式或靜態初始化程式塊中的程式碼時,方法名稱將為“”。

(在某些版本的 Java 中,堆疊跟蹤格式化程式碼將檢測並忽略重複的堆疊幀序列,因為當應用程式因遞迴過多而失敗時可能會發生這種情況。)

異常連結和巢狀堆疊跟蹤

Version >= Java SE 1.4

異常連結發生在一段程式碼捕獲異常,然後建立並丟擲一個新異常,將第一個異常作為原因傳遞。這是一個例子:

File: Test,java
1   public class Test {
2      int foo() {
3           return 0 / 0;
4      }
5
6       public Test() {
7           try {
8               foo();
9           } catch (ArithmeticException ex) {
10              throw new RuntimeException("A bad thing happened", ex);
11          }
12      }
13
14      public static void main(String[] args) {
15          new Test();
16      }
17  }

編譯並執行上面的類時,我們得到以下 stacktrace:

Exception in thread "main" java.lang.RuntimeException: A bad thing happened
        at Test.<init>(Test.java:10)
        at Test.main(Test.java:15)
Caused by: java.lang.ArithmeticException: / by zero
        at Test.foo(Test.java:3)
        at Test.<init>(Test.java:8)
        ... 1 more

stacktrace 以類名,方法和呼叫堆疊開頭,用於異常(在這種情況下)導致應用程式崩潰。接下來是“引起:”行,報告 cause 異常。報告類名和訊息,然後是 cause 異常的堆疊幀。跟蹤以“…… N more”結束,表示最後 N 幀與前一個異常相同。

當主要異常的 cause 不是 tihuan 時,“引起:”僅包含在輸出中。異常可以無限連結,在這種情況下,堆疊跟蹤可以有多個“引起:”跟蹤。

注意:cause 機制僅在 Java 1.4.0 中的 Throwable API 中公開。在此之前,異常連結需要由應用程式使用自定義異常欄位來表示原因以及自定義 printStackTrace 方法來實現。

將 stacktrace 捕獲為 String

有時,應用程式需要能夠將堆疊跟蹤捕獲為 Java String,以便它可以用於其他目的。這樣做的一般方法是建立一個臨時的 OutputStreamWriter,它寫入記憶體緩衝區並將其傳遞給 printStackTrace(...)

Apache 的百科全書番石榴庫提供用於捕獲棧跟蹤作為字串的實用方法:

org.apache.commons.lang.exception.ExceptionUtils.getStackTrace(Throwable)

com.google.common.base.Throwables.getStackTraceAsString(Throwable)

如果你不能在程式碼庫中使用第三方庫,那麼執行以下任務的方法:

   /**
     * Returns the string representation of the stack trace.
     *
     * @param throwable the throwable
     * @return the string.
     */
    public static String stackTraceToString(Throwable throwable) {
        StringWriter stringWriter = new StringWriter();
        throwable.printStackTrace(new PrintWriter(stringWriter));
        return stringWriter.toString();
    }

請注意,如果你的目的是分析堆疊跟蹤,則使用 getStackTrace()getCause() 比嘗試解析堆疊跟蹤更簡單。