Java创建和读取堆栈跟踪

示例

当创建异常对象时(即当您创建异常对象时new),Throwable构造函数将捕获有关在其中创建异常的上下文的信息。稍后,该信息可以以堆栈跟踪的形式输出,该堆栈跟踪可用于帮助诊断最初导致异常的问题。

打印堆栈跟踪

打印stacktrace只需调用该printStackTrace()方法即可。例如:

try {
    int a = 0;
    int b = 0;
    int c = a / b;
} catch (ArithmeticException ex) {
    // 这会将stacktrace打印到标准输出
    ex.printStackTrace();
}

printStackTrace()不带参数的方法将打印到应用程序的标准输出。即当前System.out。还有printStackTrace(PrintStream)和printStackTrace(PrintWriter)重载可打印到指定的Stream或Writer。

笔记:

  1. stacktrace不包含异常本身的详细信息。您可以使用该toString()方法来获取这些详细信息。例如

       // 打印异常和堆栈跟踪
      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,并且异常消息是“ /零”。

如果我们在Javadocs中查找此异常,则表示:

在发生异常算术条件时抛出。例如,整数“除以零”将引发此类的实例。

确实,消息“ /被零”强烈暗示了异常的原因是某些代码试图将某物除以零。但是呢

其余3行是堆栈跟踪。每行代表调用堆栈上的一个方法(或构造函数)调用,每一行告诉我们三件事:

  • 正在执行的类和方法的名称,

  • 源代码文件名,

  • 正在执行的语句的源代码行号

堆栈跟踪的这些行在顶部列出了当前调用的框架。上面我们示例中的顶部框架位于Test.bar方法中,位于Test.java文件的第9行。这是以下行:

    return a / b;

如果我们在文件中较早的地方看几行b,b将其初始化到哪里,那么显然它将具有零值。我们可以毫无疑问地说这是例外的原因。

如果需要更进一步,我们可以bar()从foo()在Test.java的第3行调用的stacktrace中看到foo(),然后从中调用。Main.main()

注意:堆栈框中的类和方法名称是类和方法的内部名称。您需要识别以下几种异常情况:

  • 嵌套类或内部类将看起来像“ OuterClass $InnerClass”。

  • 匿名内部类将看起来像“ OuterClass $1”,“ OuterClass $2”等。

  • 当执行构造函数,实例字段初始化程序或实例初始化程序块中的代码时,方法名称将为“”。

  • 当执行静态字段初始化程序或静态初始化程序块中的代码时,方法名称将为“”。

(在某些Java版本中,stacktrace格式代码将检测并消除重复的堆栈帧序列,这可能在应用程序由于过度递归而失败时发生。)

异常链接和嵌套堆栈跟踪

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  }

编译并运行上述类后,我们将获得以下堆栈跟踪:

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

stacktrace以类名,方法和调用堆栈开头,以表示导致应用程序崩溃的异常(在这种情况下)。这之后是“ Caused by:”行,报告了cause异常。报告类名和消息,后跟原因异常的堆栈帧。跟踪以“ ... N更多”结尾,表示最后N帧与先前的异常相同。

仅当主要例外cause不是时,“ Causeed by:”才包含在输出中null。异常可以无限地链接,在这种情况下,堆栈跟踪可以具有多个“由...引起”跟踪。

注意:该cause机制仅Throwable在Java 1.4.0中的API中公开。在此之前,应用程序需要使用代表异常原因的自定义异常字段和自定义printStackTrace方法来实现异常链接。

将stacktrace捕获为字符串

有时,应用程序需要能够将stacktrace捕获为Java String,以便可以将其用于其他目的。执行此操作的一般方法是创建一个临时文件OutputStream或将Writer其写入内存缓冲区并将其传递给printStackTrace(...)。

Apache Commons和Guava库提供了用于将stacktrace捕获为String的实用方法:

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(),而不是试图解析堆栈跟踪。