天天看點

Throwable源碼異常分析1.Throwable包含哪些成員變量?2.Throwable的構造函數3.fillInStackTrace()方法4.addSuppressed()方法和getSuppressed()方法5.PrintStackTrace

問題:

Throwable是所有異常的父類,那麼異常到底包含哪些資訊呢?

1.Throwable包含哪些成員變量?

public class Throwable implements Serializable {
    private transient volatile Object backtrace;
    //異常資訊
    private String detailMessage;
    //目前異常是由哪個Throwable所引起的
    private Throwable cause = this;
    //引起異常的堆棧跟蹤資訊
    private StackTraceElement[] stackTrace = libcore.util.EmptyArray.STACK_TRACE_ELEMENT;
}
           

backtrace:這個變量由native方法指派,用來儲存棧資訊的軌迹;

detailMessage:這個變量是描述異常資訊,比如new  InsertFailException("can't insert table"),記錄的是傳進去描述此異常的描述資訊"can't insert table";

case:記錄目前異常是由哪個異常所引起的,預設是this,可通過構造器自定義;可以通過initCase方法進行修改:

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;
    }
           

可以看到case隻能被修改一次,當發現case已經被修改,則會抛出IllegalStateException異常;預設case=this,如果再次修改case為this也是不允許的;

case一般這樣使用:

try {
          lowLevelOp();
      } catch (LowLevelException le) {
          throw (HighLevelException)
                new HighLevelException().initCause(le); // Legacy constructor
      }
           

stackTrace:記錄目前異常堆棧資訊,數組中每一個StackTraceElement表示目前方法調用的一個棧幀,表示一次方法調用;StackTraceElement中儲存的有目前方法的類名,方法名,檔案名,行号資訊;

public final class StackTraceElement implements java.io.Serializable {
    // Normally initialized by VM (public constructor added in 1.5)
    private String declaringClass;
    private String methodName;
    private String fileName;
    private int    lineNumber;
    
    public String toString() {
        // Android-changed: When ART cannot find a line number, the lineNumber field is set
        // to the dex_pc and the fileName field is set to null.
        StringBuilder result = new StringBuilder();
        result.append(getClassName()).append(".").append(methodName);
        if (isNativeMethod()) {
            result.append("(Native Method)");
        } else if (fileName != null) {
            if (lineNumber >= 0) {
                result.append("(").append(fileName).append(":").append(lineNumber).append(")");
            } else {
                result.append("(").append(fileName).append(")");
            }
        } else {
            if (lineNumber >= 0) {
                // The line number is actually the dex pc.
                result.append("(Unknown Source:").append(lineNumber).append(")");
            } else {
                result.append("(Unknown Source)");
            }
        }
        return result.toString();
    }
}
           

下面代碼為列印堆棧資訊:

public class Main {
	public static void main(String[] args) {
		b();
	}
	public static void b() {
		Throwable th = new Throwable();
		for (StackTraceElement e : th.getStackTrace()) {
			System.out.println(e);
		}
	}
}
-------運作結果------
com.gome.test.exception.Main.b(Main.java:24)
com.gome.test.exception.Main.main(Main.java:20)
           

2.Throwable的構造函數

public Throwable() {
        fillInStackTrace();
    }

    public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }

    public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }

    public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }

    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()方法;

3.fillInStackTrace()方法

public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            backtrace = nativeFillInStackTrace();
            stackTrace = libcore.util.EmptyArray.STACK_TRACE_ELEMENT;
        }
        return this;
    }
           

fillInStackTrace會首先判斷stackTrace是否為null,如果不為null則會調用native方法nativeFillInStackTrace()将目前線程的棧幀資訊記錄到此Throwable中,那麼什麼時候為null呢,答案是上面的protected構造器可以指定writableStackTrace為false,這樣stackTrace就為null了,就不會調用nativeFillInStackTrace擷取堆棧資訊。

nativeFillInStackTrace将目前線程的棧幀資訊記錄到此Throwable中,為了了解我們看一個例子:

正常情況下我們抛出RuntimeException,異常列印是帶有異常堆棧資訊的:

package com.gome.childrenmanager;

public class InsertUserException extends RuntimeException{
    public static void insert1(){
        System.out.println("Method:insert1()");
        insert2();
    }

    public static void insert2(){
        System.out.println("Method:insert2()");
        insert3();
    }

    public static void insert3(){
        System.out.println("Method:insert3()");
        insert4();
    }
    public static void insert4(){
        throw new InsertUserException();
    }

    public static void main(String[] args) {
        insert1();
    }
}
           

運作結果:

Method:insert1()
Method:insert2()
Method:insert3()
Class transformation time: 0.0137006s for 112 classes or 1.2232678571428573E-4s per class
Exception in thread "main" com.gome.childrenmanager.InsertUserException
	at com.gome.childrenmanager.InsertUserException.insert4(InsertUserException.java:19)
	at com.gome.childrenmanager.InsertUserException.insert3(InsertUserException.java:16)
	at com.gome.childrenmanager.InsertUserException.insert2(InsertUserException.java:11)
	at com.gome.childrenmanager.InsertUserException.insert1(InsertUserException.java:6)
	at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:23)
           

我們來重寫fillInStackTrace()方法,看一下運作效果:

public class InsertUserException extends RuntimeException{
    
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }

    public static void insert1(){
        System.out.println("Method:insert1()");
        insert2();
    }

    public static void insert2(){
        System.out.println("Method:insert2()");
        insert3();
    }

    public static void insert3(){
        System.out.println("Method:insert3()");
        insert4();
    }
    public static void insert4(){
        throw new InsertUserException();
    }

    public static void main(String[] args) {
        insert1();
    }
}
           

輸出:

Method:insert1()
Method:insert2()
Method:insert3()
Class transformation time: 0.0099176s for 112 classes or 8.855E-5s per class
Exception in thread "main" com.gome.childrenmanager.InsertUserException
           

從例子可以看到**fillInStackTrace作用是将目前線程的棧幀資訊記錄到此Throwable中**。

4.addSuppressed()方法和getSuppressed()方法

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.isEmpty())
            suppressedExceptions = new ArrayList<>(1);

        suppressedExceptions.add(exception);
    }

    private static Throwable[] EMPTY_THROWABLE_ARRAY;

    public final synchronized Throwable[] getSuppressed() {
        if (EMPTY_THROWABLE_ARRAY == null) {
            EMPTY_THROWABLE_ARRAY = new Throwable[0];
        }

        if (suppressedExceptions == null || suppressedExceptions.isEmpty())
            return EMPTY_THROWABLE_ARRAY;
        else
            return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
    }
           

如果try中抛出異常,在執行流程轉移到方法棧上一層之前,finall語句塊會執行,但是,如果在finally語句塊中又抛出了一個異常,那麼這個異常會覆寫掉之前抛出的異常,這有點像finally中return被覆寫了,比如下面這個例子:

public static void main(String[] args) {
        try{
            Integer.valueOf("one");
        }catch (NumberFormatException e){
            throw new RuntimeException("One", e);
        }finally {
            try{
                Integer.valueOf("two");
            }catch (NumberFormatException e){
                throw new RuntimeException("Two", e);
            }
        }
    }
           

輸出:

Exception in thread "main" java.lang.RuntimeException: Two
	at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:39)
Caused by: java.lang.NumberFormatException: For input string: "two"
	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.gome.childrenmanager.InsertUserException.main(InsertUserException.java:37)
           

Throwable對象提供了addSupperssed和getSupperssed方法,允許把finally語句塊中産生的異常通過addSupperssed方法添加到try語句産生的異常中。

public static void main(String[] args) {
        RuntimeException e1 = null;
        try{
            Integer.valueOf("one");
        }catch (NumberFormatException e){
            e1 =new RuntimeException("One", e);
            throw e1;
        }finally {
            try{
                Integer.valueOf("two");
            }catch (NumberFormatException e){
                RuntimeException e2 = new RuntimeException("Two", e);
                e1.addSuppressed(e2);
                throw e1;
            }
        }
    }
           

輸出:

Exception in thread "main" java.lang.RuntimeException: One
	at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:35)
	Suppressed: java.lang.RuntimeException: Two
		at com.gome.childrenmanager.InsertUserException.main(InsertUserException.java:41)
	Caused by: java.lang.NumberFormatException: For input string: "two"
		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.gome.childrenmanager.InsertUserException.main(InsertUserException.java:39)
Caused by: java.lang.NumberFormatException: For input string: "one"
	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.gome.childrenmanager.InsertUserException.main(InsertUserException.java:33)
Class transformation time: 0.0109227s for 113 classes or 9.666106194690266E-5s per class
           

5.PrintStackTrace

printStackTrace()方法分四個方面列印出目前異常資訊

1)列印出目前異常的詳細資訊

2) 列印出異常堆棧中的棧幀資訊

3) 列印出suppress異常資訊

4) 遞歸列印出引起目前異常的異常資訊

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


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

    private void printStackTrace(PrintStreamOrWriter s) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        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);

            // 列印suppressed exceptions
            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);
        }
    }

    /**
     * Print our stack trace as an enclosed exception for the specified
     * stack trace.
     */
    private void printEnclosedStackTrace(PrintStreamOrWriter s,
                                         StackTraceElement[] enclosingTrace,
                                         String caption,
                                         String prefix,
                                         Set<Throwable> dejaVu) {
        if (dejaVu.contains(this)) {
            s.println("\t[CIRCULAR REFERENCE:" + this + "]");
        } else {
            dejaVu.add(this);
            // Compute number of frames in common between this and enclosing trace
            StackTraceElement[] trace = getOurStackTrace();
            int m = trace.length - 1;
            int n = enclosingTrace.length - 1;
            while (m >= 0 && n >=0 && trace[m].equals(enclosingTrace[n])) {
                m--; n--;
            }
            int framesInCommon = trace.length - 1 - m;

            // Print our stack trace
            s.println(prefix + caption + this);
            for (int i = 0; i <= m; i++)
                s.println(prefix + "\tat " + trace[i]);
            if (framesInCommon != 0)
                s.println(prefix + "\t... " + framesInCommon + " more");

            // Print suppressed exceptions, if any
            for (Throwable se : getSuppressed())
                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION,
                                           prefix +"\t", dejaVu);

            // Print cause, if any
            Throwable ourCause = getCause();
            if (ourCause != null)
                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, prefix, dejaVu);
        }
    }
           

參考:

https://blog.csdn.net/weixin_39787628/article/details/111118098

https://zhuanlan.zhihu.com/p/402638614

繼續閱讀