创建和阅读堆栈跟踪

当创建异常对象时(即,当你创建异常对象时),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() 比尝试解析堆栈跟踪更简单。