JAVA异常与调试——堆栈跟踪元素分析

一、简要介绍

Java堆栈跟踪元素分析在异常与调试中很常见,特别是当你的程序有异常抛出的时候,打印出来的一系列堆栈信息,就是堆栈跟踪元素分析的结果。接下来是一些堆栈跟踪元素分析的学习笔记(JAVA核心技术|上卷|11.2.4)与自己理解体会。堆栈跟踪(stack trace)是一个方法调用过程的列表,它包含程序执行过程中方法调用特定位置。注意,堆栈只是跟踪抛出异常的语句,而不必跟踪错误的根源。 Java中Error类以及Exception类都是Throwable类的子类。所以一切都可以用Throwable类来讨论。可以通过Throwable对象调用getStackTrace方法获得一个StackTraceElement对象的数组,然后对这个对象进行分析。接下来一一分析。 ## 二、Throwable类

Throwable类是Error类和Exception类的超类。其中的getStackTrace方法可以获得StackTraceElement(见第三部分)对象的数组,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Throwable(String message, Throwable cause)
//用给定的诱饵构造一个Throwable对象
Throwable initCause(Throwable cause)
//将这个对象设置为“诱饵”,如果这个对象已经被设置为“诱饵”,就抛出一个异常,返回this引用。
Throwable getCause()
//获取设置为这个对象的“诱饵”的异常对象,如果没有设置返回null。
StackTraceElement[] getStackTrace()
//获取构造对象时调用的堆栈的跟踪信息
//简单例子代码
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement stackTraceElement : frames) {
//分析对象,如打印信息。
//System.out.println(stackTraceElement);
}

获取到stackTraceElement对象数组后就可以对其中的元素进行分析,接下来看看stackTraceElement类。

三、stackTraceElement类

stackTraceElement类含有获取文件名和当前执行的代码的行号的方法、获取类名的方法和获取方法名的方法等。在JDK5.0中,增加了静态方法Thread.getAllStackTrace方法用于获取所有线程的堆栈跟踪。以下是stacktraceElement类的常见方法:

1
2
3
4
5
6
7
8
9
10
String getFileName()
//返回这个元素运行时对应的源文件名,如果不存在返回null。
int getLineNumber()
//返回这个元素运行时对应的源文件行数,如果不存在返回-1。
String getClassName()
//返回这个元素运行时对应的类的全名。
String getMethodName()
//返回这个元素运行时对应的方法名。构造器名是<int>;静态初始化器名是<clinit>。无法区分同名的重载方法。
boolean isNativeMethod()
//如果这个元素运行时在一个本地方法中,返回true。

 四、例子代码与分析

跟踪递归阶乘方法的堆栈信息打印情况,见以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class StackTraceDemo {
public static int factorial(int n){
System.out.println("factorial(" + n + "):");
Throwable t = new Throwable();
StackTraceElement[] frames = t.getStackTrace();
for (StackTraceElement stackTraceElement : frames) {
System.out.println(stackTraceElement);//打印堆栈跟踪信息
}
int r;
if (n == 1) {
r = 1;
}else {
r = n * factorial(n - 1);//递归
}
System.out.println("return " + r);
return r;
}
public static void main(String args[]){
Scanner in = new Scanner(System.in);
System.out.print("Enter n: ");
int n = in.nextInt();
factorial(n);
in.close();
}
}

计算factorial(3),结果如下:

分析:输入3后,开始计算3的阶乘,每计算一次阶乘打印出当前计算的阶乘的堆栈跟踪信息,包括源代码文件名、源码所在行数以及所经历的类名、方法名。

五、封装使用

下面是封装一个测试流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class StackTraceDemo {
private static Trace trace;
public static void main(String args[]){
trace = new Trace();
new StackTraceDemo().traceTest();
}
private void traceTest(){//对于每个方法都可以进行这样的测试
trace.methodStart();
trace.methodToString("is a string");
trace.methodEnd();
}
}
class Trace{
//方法开始,打印
public void methodStart(){
//每个方法中都需要重新定义stacktraceElement变量,不然打印出的源代码所在行号会出现错误
StackTraceElement stackTraceElement = (new Exception()).getStackTrace()[1];//0,打印方法代码具体行数;
//1,打印信息信息;
//2方法调用结束后,返回到的行号
System.out.print("[" + stackTraceElement.toString() + "]--methodStart" + "n");
}
//方法结束,打印
public void methodEnd(){
StackTraceElement stackTraceElement = (new Exception()).getStackTrace()[1];
System.out.print("[" + stackTraceElement.toString() + "]--methodEnd" + "n");
}
//方法转换string,打印
public void methodToString(String string){
StackTraceElement stackTraceElement = (new Exception()).getStackTrace()[1];
System.out.print("[" + stackTraceElement.toString() + "]--methodToString--" + string + "n");
}
}

结果:

以后,在程序的调试过程中,可以利用stackTraceElement类对需要的方法进行测试以查看程序的流程。