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。
笔记:
stacktrace不包含异常本身的详细信息。您可以使用该toString()方法来获取这些详细信息。例如
//打印异常和堆栈跟踪
System.out.println(ex);
ex.printStackTrace();
应当谨慎使用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格式代码将检测并消除重复的堆栈帧序列,这可能在应用程序由于过度递归而失败时发生。)
异常链接和嵌套堆栈跟踪
当一段代码捕获到一个异常,然后创建并抛出一个新的异常并将第一个异常作为原因时,就会发生异常链接。这是一个例子:
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以类名,方法和调用堆栈开头,以表示导致应用程序崩溃的异常(在这种情况下)。这之后是“Causedby:”行,报告了cause异常。报告类名和消息,后跟原因异常的堆栈帧。跟踪以“...N更多”结尾,表示最后N帧与先前的异常相同。
仅当主要例外cause不是时,“Causeedby:”才包含在输出中null。异常可以无限地链接,在这种情况下,堆栈跟踪可以具有多个“由...引起”跟踪。
注意:该cause机制仅Throwable在Java1.4.0中的API中公开。在此之前,应用程序需要使用代表异常原因的自定义异常字段和自定义printStackTrace方法来实现异常链接。
将stacktrace捕获为字符串
有时,应用程序需要能够将stacktrace捕获为JavaString,以便可以将其用于其他目的。执行此操作的一般方法是创建一个临时文件OutputStream或将Writer其写入内存缓冲区并将其传递给printStackTrace(...)。
ApacheCommons和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(),而不是试图解析堆栈跟踪。