java.lang.Throwable源码解析(JDK1.8)

版权声明:转载请注明出处 https://blog.csdn.net/abc123lzf/article/details/82115048

一、基础知识复习

Throwable是所有异常类的父类,它提供了一系列API为我们反馈异常的信息。

在分析Throwable源码前,我们先来回顾一下Java异常的基础知识:
1、Java提供了try…catch…finally…语法来实现异常处理机制,在try语句中一旦抛出一个异常对象(Throwable或其子类),如果找到了对应的catch语句块,都可以将其捕获并将异常对象作为形参传入catch语句块中进行处理,如果没有找到合适的catch语句块,则会使当前线程停止。无论有没有异常,finally语句块都会执行(除了强制退出Java程序)。
2、Java将异常分为两类:异常(Exception)和错误(Error),它们都继承Throwable类。Error一般指与JVM相关的问题:如崩溃、虚拟机内部错误等,这种错误无法恢复,会导致应用程序中断,一般情况下程序不需要捕获Error。Exception一般指程序可以预知的异常并且可以正常的处理,这种类型的异常需要捕获并处理。
3、Java的Exception还可以分为两类:Checked异常类和Runtime异常类,所有RuntimeException类及其子类被称为Runtime异常类,不是RuntimeException子类并且以Exception为父类的异常类为Checked异常类。对于Checked异常,程序必须由try…catch语句来捕获此异常,如果没有捕获则无法通过编译。而Runtime异常则不一样,Runtime异常无须显式声明抛出,当然也可以通过try…catch捕获。常见的RuntimeException子类有IndexOutOfBoundsException, NumberFormatException。
4、在Java 7以后的版本,可以将实现了Closeable接口的类(比如InputStream)放在try(…){}小括号中,这样可以在try语句块执行完毕后自动关闭它们,而不必在finally语句显式关闭。
5、子类方法声明抛出的异常类型应当是父类方法声明抛出的异常类型的子类或是同一个类,子类方法声明抛出的异常不允许比父类方法声明抛出的多。
6、不要在finally块调用return,finally中的return会覆盖掉try…catch…的返回值,因为finally语句实际上是在return语句在真正返回之前执行。

二、源码解析

分析Exception和Error类源码,发现这些类都只是调用了父类Throwable的构造器。
既然Exception和Error继承了java.lang.Throwable类,所以了解Throwable类的源码是十分必要的

下面通过我们先通过成员变量来逐步了解Throwable类:
//静态变量
private static class SentinelHolder {
    public static final StackTraceElement STACK_TRACE_ELEMENT_SENTINEL =
        new StackTraceElement("", "", null, Integer.MIN_VALUE);
    public static final StackTraceElement[] STACK_TRACE_SENTINEL =
        new StackTraceElement[] {STACK_TRACE_ELEMENT_SENTINEL};
}

private static final StackTraceElement[] UNASSIGNED_STACK 
        = new StackTraceElement[0];
private static final List<Throwable> SUPPRESSED_SENTINEL =
        Collections.unmodifiableList(new ArrayList<Throwable>(0));
private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";
private static final String SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted";
private static final String CAUSE_CAPTION = "Caused by: ";
private static final String SUPPRESSED_CAPTION = "Suppressed: ";
private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];

//实例变量
private transient Object backtrace;
private String detailMessage;
private Throwable cause = this;
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;

实例变量:
(1)Object backtrace :这个变量由native方法赋值,用来保存栈信息的轨迹(根据注释翻译的,具体作用不太清楚)。
(2)String detailMessage :描述这个异常的信息,比如new Exception(“This is exception”)传入的就是这个异常的描述信息,默认条件下可以调用getMessage或getLocalizedMessage方法获取。

public String getMessage() {
    return detailMessage;
}
public String getLocalizedMessage() {
    return getMessage();
}

这两个方法都可以被子类重写,一般来说,getLocalizedMessage用来返回本地化描述信息,如果子类有必要实现本地化错误描述信息的话可以重写这个方法。

(3)Throwable cause :描述这个异常由哪个Throwable导致,默认是this。可通过构造器自定义。构造完成后,可以通过getCause方法访问:

public synchronized Throwable getCause() {
    return (cause == this ? null : cause);
}

如果没有指定cause,则返回null。
在构造完成后,也可通过initCause方法修改:

public synchronized Throwable initCause(Throwable cause) {
    if (this.cause != this)
        throw new IllegalStateException("Can't overwrite cause with " +
                                            Objects.toString(cause, "a null"), this);
    if (cause == this)
        throw new IllegalArgumentException("Self-causation not permitted", this);
    this.cause = cause;
    return this;
}

修改cause的前提是必须在构造方法中没有指定别的cause(即默认条件下cause为this),否则会抛出IllegalStateException异常。
另外也不能修改cause为this。

cause一般用在这种情形中:

try {
    Integer.valueOf(str);
} catch(NumberFormatException e) {
    throw new MyException(e);
}

相当于给NumberFormatException加了一层包装,以便更好地处理异常(因为只需要捕获到一种异常类型MyException)

(4)StackTraceElement[] stackTrace :这个异常抛出位置的栈信息,每个StackTraceElement代表一个栈信息,默认指向静态常量UNASSIGNED_STACK,代表栈信息为空。
我们先来看StackTraceElement源代码

public final class StackTraceElement implements java.io.Serializable {

    private String declaringClass;
    private String methodName;
    private String fileName;
    private int lineNumber;

    public StackTraceElement(String declaringClass, String methodName,
                             String fileName, int lineNumber) {
        this.declaringClass = Objects.requireNonNull(declaringClass, "Declaring class is null");
        this.methodName = Objects.requireNonNull(methodName, "Method name is null");
        this.fileName = fileName;
        this.lineNumber = lineNumber;
    }

    public String getFileName() {
        return fileName;
    }

    public int getLineNumber() {
        return lineNumber;
    }

    public String getClassName() {
        return declaringClass;
    }

    public String getMethodName() {
        return methodName;
    }

    public boolean isNativeMethod() {
        return lineNumber == -2;
    }

    public String toString() {
        return getClassName() + "." + methodName +
            (isNativeMethod() ? "(Native Method)" :
             (fileName != null && lineNumber >= 0 ?
              "(" + fileName + ":" + lineNumber + ")" :
              (fileName != null ?  "("+fileName+")" : "(Unknown Source)")));
    }

    //省略equals、hashCode方法
}

StackTraceElement保存了一个栈区单个方法方法名、所属类名、代码行数(可为null,视调用的方法),文件名(可为null,视调用的方法)。虽然StackTraceElement提供了public构造器,但该类对象一般还是通过JVM构造的(通过调用Throwable实例的getStackTraceElement(int index)本地方法设置到stackTrace成员变量)。

我们可以调用getStackTrace方法获取到stackTrace数组的副本,里面包含了栈区方法的信息。

public StackTraceElement[] getStackTrace() {
    return getOurStackTrace().clone();
}

//printStackTrace方法, writeObject(用于序列化)也会调用此方法获取栈信息
private synchronized StackTraceElement[] getOurStackTrace() {
    if (stackTrace == UNASSIGNED_STACK || (stackTrace == null && backtrace != null)) {
        int depth = getStackTraceDepth();
        stackTrace = new StackTraceElement[depth];
        for (int i=0; i < depth; i++)
            stackTrace[i] = getStackTraceElement(i);
    } else if (stackTrace == null) {
        return UNASSIGNED_STACK;
    }
    return stackTrace;
}

native int getStackTraceDepth();
native StackTraceElement getStackTraceElement(int index);

栈信息、栈深度都是通过本地方法获取的,这里就不再讨论JVM相关的实现了。

(5)List<Throwable> suppressedExceptions : 这个是JDK 1.7引入的新特性。该List用来保存被屏蔽的异常对象,在try-catch语句中,如果try中抛出了异常,在执行流程转移到方法栈上一层之前,finally语句块会执行,但是,如果在finally语句块中又抛出了一个异常,那么这个异常会覆盖掉之前抛出的异常,这点很像finally中return的覆盖。比如下面这个例子:

public class Test {
    static class MyException extends Exception {
        public MyException(String string, Exception e) {
            super(string, e);
        }
    }
    private static void test() throws MyException {
        try {
            Integer.valueOf("oh");
        } catch(NumberFormatException e) {
            throw new MyException("One", e); //在JDK1.7以前,这个异常会被覆盖掉
        } finally {
            try {
                Integer.valueOf("oh");
            } catch(NumberFormatException e) {
                throw new MyException("Two", e); //finally语句抛出异常
            }
        }
    }
    public static void main(String[] args) {
        try {
            test();
        } catch (MyException e) {
            e.printStackTrace();
        }
    }
}

运行上述代码,控制台输出:

com.test.Test$MyException: Two
    at com.test.Test.test(Test.java:42)
    at com.test.Test.main(Test.java:49)
Caused by: java.lang.NumberFormatException: For input string: "oh"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at com.test.Test.test(Test.java:40)
    ... 1 more

发现没有,之前MyException(“One”, e)被第二个MyException覆盖掉了。
在JDK1.7以前,有两种解决方案:如果只关心第一个异常,那么我们可以再try语句块之前用一个局部变量保存第一次抛出的Exception,在finally语句块捕获异常的时候,判断这个局部变量是否为null,如果不为null则抛出局部变量保存的异常对象,否则直接抛出第二个异常对象。另外一种是继承Exception类并添加一个List或其它容器保存已经抛出的异常对象,然后在上一层方法中通过循环依次处理。
在JDK1.7以后,Throwable对象提供了addSupperssed和getSupperssed方法,允许把finally语句块中产生的异常通过addSupperssed方法添加到try语句产生的异常中。

public final synchronized void addSuppressed(Throwable exception) {
    if (exception == this)
        throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);
    if (exception == null)
        throw new NullPointerException(NULL_CAUSE_MESSAGE);
    if (suppressedExceptions == null) // Suppressed exceptions not recorded
        return;
    if (suppressedExceptions == SUPPRESSED_SENTINEL)
        suppressedExceptions = new ArrayList<>(1);
    suppressedExceptions.add(exception);
}

addSuppressed方法不能传入自身,也不可为空。如果没有设置过suppressedExceptions(默认为SUPPRESSED_SENTINEL),则会给suppressedExceptions构造一个ArrayList,并将异常添加进去。

public final synchronized Throwable[] getSuppressed() {
    if (suppressedExceptions == SUPPRESSED_SENTINEL || suppressedExceptions == null)
        return EMPTY_THROWABLE_ARRAY;
    else
        return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
}

getSuppressed方法会首先判断有没有添加过suppressed,如果没有,则返回一个空数组,否则将suppressedExceptions成员变量的值拷贝成一个Throwable[]数组然后返回。

为了更好的理解这个功能的作用,我们将上述例子改写为:

private static void test() throws MyException {
    MyException ex = null;
    try {
        Integer.valueOf("oh");
    } catch(NumberFormatException e) {
        ex = new MyException("One", e);
        throw ex;
    } finally {
        try {
            Integer.valueOf("oh");
        } catch(NumberFormatException e) {
            MyException ex2 = new MyException("Two", e);
            if(ex != null) {
                ex.addSuppressed(ex2);
                throw ex;
            }
            throw ex2;
        }
    }
}   
public static void main(String[] args) {
    try {
        test();
    } catch (MyException e) {
        e.printStackTrace();
    }
}

这样,main方法获取到的异常对象e就包含了test方法中两个异常对象,输出结果为:

com.test.Test$MyException: One
    at com.test.Test.test(Test.java:38)
    at com.test.Test.main(Test.java:56)
    Suppressed: com.test.Test$MyException: Two
        at com.test.Test.test(Test.java:44)
        ... 1 more
    Caused by: java.lang.NumberFormatException: For input string: "oh"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.valueOf(Integer.java:766)
        at com.test.Test.test(Test.java:42)
        ... 1 more
Caused by: java.lang.NumberFormatException: For input string: "oh"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at com.test.Test.test(Test.java:36)
    ... 1 more

addSuppressed方法添加的异常对象在打印堆栈信息时会加上:”Suppressed:”前缀。

静态变量:
(1)StackTraceElement SentinelHolder.STACK_TRACE_ELEMENT_SENTINEL
和 StackTraceElement[] SentinelHolder.STACK_TRACE_SENTINEL
这两个变量主要用于序列化,当序列化出的stackTrace的长度为1时且这个唯一的元素等价于STACK_TRACE_ELEMENT_SENTINEL时,将stackTrace成员变量设为null,这里不详细讨论了
(2)StackTraceElement[] UNASSIGNED_STACK
一个空的StackTraceElement[]数组,用来初始化或者作为返回值。
(3)List<Throwable> SUPPRESSED_SENTINEL
一个空的只读List,同样用于初始化
(4)String NULL_CAUSE_MESSAGE、
SELF_SUPPRESSION_MESSAGE、CAUSE_CAPTION、SUPPRESSED_CAPTION
前两个用于作为错误信息,后两个作为printStackTrace方法的说明前缀使用
(5)Throwable[] EMPTY_THROWABLE_ARRAY
用作getSuppressed方法的返回值(当suppressedExceptions没有元素时)

构造方法分析:

分析完这些变量后,相信大家已经对Throwable类已经有了一个初步的了解,下面我们再来分析构造方法。

public Throwable() {
    fillInStackTrace();
}
/**
 * @param message 异常描述信息,该参数直接赋值给实例变量detailMessage
 */
public Throwable(String message) {
    fillInStackTrace();
    detailMessage = message;
}
/**
 * @param message 异常描述信息,该参数直接赋值给实例变量detailMessage
 * @param cause 描述当前异常由哪个异常引发
 */
public Throwable(String message, Throwable cause) {
    fillInStackTrace();
    detailMessage = message;
    this.cause = cause;
}
/**
 * @param cause 描述当前异常由哪个异常引发
 */
public Throwable(Throwable cause) {
    fillInStackTrace();
    detailMessage = (cause==null ? null : cause.toString());
    this.cause = cause;
}
/**
 * @param message 异常描述信息,该参数直接赋值给实例变量detailMessage
 * @param cause 描述当前异常由哪个异常引发
 * @param enableSuppression 是否支持Suppress异常消息
 * @param writableStackTrace 是否调用fillInStackTrace使堆栈信息可以写入
 */
protected Throwable(String message, Throwable cause,
                        boolean enableSuppression, boolean writableStackTrace) {
    if (writableStackTrace) {
        fillInStackTrace();
    } else {
        stackTrace = null;
    }
    detailMessage = message;
    this.cause = cause;
    if (!enableSuppression)
        suppressedExceptions = null;
}

Throwable提供了4个public构造器和1个protected构造器(该构造器由JDK1.7引入)。4个public构造器共同点就是都调用了fillInStackTrace方法。

public synchronized Throwable fillInStackTrace() {
    if (stackTrace != null || backtrace != null) {
        fillInStackTrace(0);
        stackTrace = UNASSIGNED_STACK;
    }
    return this;
}
private native Throwable fillInStackTrace(int dummy);

fillInStackTrace会首先判断stackTrace是不是为null,如果不为null则会调用native方法fillInStackTrace获取当前堆栈信息。那么什么时候为null呢,答案是上面的protected构造器可以指定writableStackTrace为false,这样stackTrace就为null了,就不会调用fillInStackTrace获取堆栈信息。如果你不需要异常的栈信息,你也可以重写这个方法,让它直接返回this,毕竟异常的爬栈是一个开销比较大的操作。

下面给出一个fillInStackTrace使用的例子:

private static void throwException() throws MyException {
    throw new MyException();
}

private static void test() throws Throwable {
    try {
        throwException();
    } catch(MyException e) {
        e.printStackTrace();
        throw e.fillInStackTrace();
    }
}

public static void main(String[] args) {
    try {
        test();
    } catch (Throwable e) {
        e.printStackTrace();
    }
}

控制台输出:

com.test.Test$MyException
    at com.test.Test.throwException(Test.java:43)
    at com.test.Test.test(Test.java:48)
    at com.test.Test.main(Test.java:57)
com.test.Test$MyException
    at com.test.Test.test(Test.java:51)
    at com.test.Test.main(Test.java:57)

printStackTrace方法

很多Java新手在处理异常的时候就是直接调用此方法打印异常堆栈信息,这个方法在调试过程中很有用,但是在生产环境中就不太适用了。
下面我们来分析源码:

public void printStackTrace() {
    printStackTrace(System.err);
}

public void printStackTrace(PrintStream s) {
    printStackTrace(new WrappedPrintStream(s));
}

public void printStackTrace(PrintWriter s) {
    printStackTrace(new WrappedPrintWriter(s));
}

private void printStackTrace(PrintStreamOrWriter s) {
    Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
    dejaVu.add(this);

    synchronized (s.lock()) {
        s.println(this);
        StackTraceElement[] trace = getOurStackTrace();
        for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

        for (Throwable se : getSuppressed())
            se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

        Throwable ourCause = getCause();
        if (ourCause != null)
            ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
    }
}
}

printStackTrace把传入的输入流用内部类WrappedPrintStream或WrappedPrintWriter包装,主要用来实现printStackTrace方法在打印堆栈信息时的线程安全。

猜你喜欢

转载自blog.csdn.net/abc123lzf/article/details/82115048
今日推荐