二、Java提升之异常源码剖析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/QQB67G8COM/article/details/81437156

定义(百度百科)

异常:程序在运行过程中发生由于外部问题(如硬件错误、输入错误)等导致的程序异常事件。
(在Java等面向对象的编程语言中)异常本身是一个对象,产生异常就是产生了一个异常对象

异常与错误的区别(百度百科)

异常都是运行时的。编译时产生的不是异常,而是错误(Error)。
需要注意的是,程序设计导致的错误(Error)不属于异常(Exception)。

异常处理机制的体系结构

Throwable:是所有Errors和Exceptions的superclass
  |- Error:系统内部错误,这类错误由系统进行处理,程序本身无需获取处理
  |- Exception:可处理的异常
      |- 非运行时异常
          |- IOException
              |- EOFException
              |- FileNotFoundException
              |- MalformedURLException
              |- UnknownHostException
          |- ClassNotFoundException
          |- CloneNotSupported
      |- RuntimeException:可以捕获的异常,也可以不捕获的异常;是those异常的超类
          |- ArithmeticException
          |- ClassCastException
          |- IllegalArgumentException
          |- IllegalStateException
          |- IndexOutOfBoundsException
          |- NoSuchElementException
          |- NullPointerException

捕获:
通过try…catch语句进行捕获异常。
通过throw抛出异常,throws向上一级调用方法抛出异常。

带着问题去探讨

1)为什么需要序列化
2)默认继承Object,怎么做到?

问题解析

序列化

定义(百度百科):序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象
目的:
1、以某种存储形式使自定义对象持久化;(硬盘储存)
2、将对象从一个地方传递到另一个地方。(网络传输)
3、使程序更具维护性。(RMI远程调用)

默认继承Object类

1、在编译时如果没有继承的父类的话,会自动为其加入object超类的相关编译信息,这样在后续虚拟机的解释的时候会识别到其继承了Object超类
2、在虚拟机解析的时候如果没有检测Object超类还是会默认其父类为Object
第一种属于编译器再处理,第二种属于虚拟机适当处理

异常源码剖析

异常源码剖析之ArithmeticException(其它的举一反三)

ArithmeticException.class
package java.lang;

/**
* 异常抛出条件:当发生异常算术条件时抛出。例如,一个整数“除零”抛出该类的实例
*/
public class ArithmeticException extends RuntimeException {
    private static final long serialVersionUID = 2256477558314496007L;

    /**
    *算术异常构造函数没有细节信息
    */
    public ArithmeticException() {
        super();
    }

    /**
    *算术异常构造函数有细节信息
     *
     * @param   s   详细的消息.
     */
    public ArithmeticException(String s) {
        super(s);
    }
}
RuntimeException.class
package java.lang;

/**
* RuntimeException是其它那些能够在Java虚拟机正常运行期间被抛出的异常的superclass。
* RuntimeException及其子类属于未检查的异常。如果Method及构造函数的执行可以抛出未
* 检查的异常并在Method或构造函数边界之外传播,则不需要在方法或构造函数的抛出字句中声明这些异常。
*/
public class RuntimeException extends Exception {
    static final long serialVersionUID = -7034897190745766939L;

    /*
    * 构造一个新的运行时异常,其细节消息为null。
    * cause没有被初始化,随后可以通过调用initCause对cause进行初始化
    */
    public RuntimeException() {
        super();
    }

    public RuntimeException(String message) {
        super(message);
    }

    public RuntimeException(String message, Throwable cause) {
        super(message, cause);
    }

    public RuntimeException(Throwable cause) {
        super(cause);
    }

    /*
    *构造一个新的运行时异常,其中包含指定的详细信息、原因、启用或禁用的抑制以及启用或禁用的可写堆栈跟踪。
    * @param  message  细节消息 
    * @param cause   原因,允许使用{@code null} value,并指示原因 nonexistent 或 unknown
    * @param enableSuppression   启用或禁用抑制
    * @param writableStackTrace    启用或禁用可写堆栈的跟踪
    */
    protected RuntimeException(String message, Throwable cause,
                               boolean enableSuppression,
                               boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
Exception.class
package java.lang;

public class Exception extends Throwable {
    static final long serialVersionUID = -3387516993124229948L;

    public Exception() {
        super();
    }

    public Exception(String message) {
        super(message);
    }

    public Exception(String message, Throwable cause) {
        super(message, cause);
    }

    public Exception(Throwable cause) {
        super(cause);
    }

    protected Exception(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
Throwable.class
package java.lang;
import  java.io.*;
import  java.util.*;

public class Throwable implements Serializable {
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -3042686055658047285L;

    //Native code保存了些这槽中的堆栈回溯
    private transient Object backtrace;

    //关于可抛出异常的具体细节。例如,对于FileNotFoundException,其中包含无法找到的文件的名称。
    private String detailMessage;

    //Holder类仅用于延迟初始化被序列化的sentinel(哨兵) object
    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];

    /**
    * 如果这个字段等于Throwable本身,说明这个Throwable的cause还没有被初始化
    * 如果cause这个抛出项不是由另一个Throwable抛出项或者是一个未知的Throwable,那cause=null
    */
    private Throwable cause = this;

    //默认为空堆栈共享值:未赋值堆栈
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;

    //设置这个静态字段会对一些java.util类引入一个可接受的初始化依赖项
    private static final List<Throwable> SUPPRESSED_SENTINEL =
        Collections.unmodifiableList(new ArrayList<Throwable>(0));

    //该列表(抑制异常)被初始化为零元素不可修改的sentinel(哨兵)列表。
    private List<Throwable> suppressedExceptions = SUPPRESSED_SENTINEL;

    /** 试图抑制空异常的Message. */
    private static final String NULL_CAUSE_MESSAGE = "Cannot suppress a null exception.";

    /** 试图抑制自己的Message. */
    private static final String SELF_SUPPRESSION_MESSAGE = "Self-suppression not permitted";

    /** Caused by:用于标注cause异常堆栈跟踪 */
    private static final String CAUSE_CAPTION = "Caused by: ";

    /** Suppressed:用于标记被抑制的异常堆栈跟踪 */
    private static final String SUPPRESSED_CAPTION = "Suppressed: ";

    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 的详细消息字符串
    public String getMessage() {
        return detailMessage;
    }

    //创建此 throwable 的本地化描述。子类可以重写此方法,以便生成特定于语言环境的消息。对于不重写此方法的子类,默认实现返回与 getMessage() 相同的结果。
    public String getLocalizedMessage() {
        return getMessage();
    }

    /**
    * getCause返回的是异常的原因,cause为Throwable本身的一个属性,cause要么是抛出异常类要么是null,如果是 
    *Throwable本身的话说明没有初始化完毕。这个cause带来的好处是可以使得java通过链式的结构来组织异常信息,
    * 不断的指向下一个异常抛出类,最后一个必为Throwable其本身,构成一条长长的链。
    * /         
    public synchronized Throwable getCause() {
        return (cause==this ? null : cause);
    }

    /**
     *
     *错误示例:
     * try {
     *     lowLevelOp();
     * } catch (LowLevelException le) {
     *     throw (HighLevelException)
     *           new HighLevelException().initCause(le); // Legacy constructor
     * }
     * 将此 throwable 的 cause 初始化为指定值。(该 Cause 是导致抛出此 throwable 的throwable。) 
     * 此方法至多可以调用一次。此方法通常从构造方法中调用,或者在创建 throwable 后立即调用。
     * 如果此 throwable 通过 Throwable(Throwable) 或 Throwable(String,Throwable) 创建,此方法不能调用。
     */
    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;
    }

    public String toString() {
        String s = getClass().getName();
        String message = getLocalizedMessage();
        return (message != null) ? (s + ": " + message) : s;
    }

    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()) {
            // Print our stack trace
            s.println(this);
            StackTraceElement[] trace = getOurStackTrace();
            for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

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

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

    private void printEnclosedStackTrace(PrintStreamOrWriter s,
                                         StackTraceElement[] enclosingTrace,
                                         String caption,
                                         String prefix,
                                         Set<Throwable> dejaVu) {
        assert Thread.holdsLock(s.lock());
        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);
        }
    }

    /**
     *
     * @param s {@code PrintWriter} to use for output
     * @since   JDK1.1
     */
    public void printStackTrace(PrintWriter s) {
        printStackTrace(new WrappedPrintWriter(s));
    }

    /**
     *用于PrintStream和PrintWriter的WrapperClass(包装器类),以启用printStackTrace的单个实现
     */
    private abstract static class PrintStreamOrWriter {
        /** Returns the object to be locked when using this StreamOrWriter */
        abstract Object lock();

        /** Prints the specified string as a line on this StreamOrWriter */
        abstract void println(Object o);
    }

    private static class WrappedPrintStream extends PrintStreamOrWriter {
        private final PrintStream printStream;

        WrappedPrintStream(PrintStream printStream) {
            this.printStream = printStream;
        }

        Object lock() {
            return printStream;
        }

        void println(Object o) {
            printStream.println(o);
        }
    }

    private static class WrappedPrintWriter extends PrintStreamOrWriter {
        private final PrintWriter printWriter;

        WrappedPrintWriter(PrintWriter printWriter) {
            this.printWriter = printWriter;
        }

        Object lock() {
            return printWriter;
        }

        void println(Object o) {
            printWriter.println(o);
        }
    }

    /*
    *填充 执行栈跟踪。这个方法在此抛出对象信息
    *这个方在Throwable Object信息中记录着关于当前线程栈结构的当前状态  
    *如果这个Throwable的the Stack trace不是可写的,那么调用这个这个方法没有任何效果
    */
    public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

    //这里修饰了一个原生方法
    private native Throwable fillInStackTrace(int dummy);


   /**
   * 提供编程访问由 printStackTrace() 输出的堆栈跟踪信息。
   * 返回堆栈跟踪元素的数组,每个元素表示一个堆栈帧。
   * 数组的第零个元素(假定数据的长度为非零)表示堆栈顶部,它是序列中最后的方法调用。
    */
    public StackTraceElement[] getStackTrace() {
        return getOurStackTrace().clone();
    }

    private synchronized StackTraceElement[] getOurStackTrace() {
        // Initialize stack trace field with information from
        // backtrace if this is the first call to this method
        if (stackTrace == UNASSIGNED_STACK ||
            (stackTrace == null && backtrace != null) /* Out of protocol state */) {
            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;
    }

     /**
     * 设置将由 getStackTrace() 返回,并由 printStackTrace() 和相关方法输出的堆栈跟踪元素。  
     * 此方法设计用于 RPC 框架和其他高级系统,允许客户端重写默认堆栈跟踪
     * /
    public void setStackTrace(StackTraceElement[] stackTrace) {
        // Validate argument
        StackTraceElement[] defensiveCopy = stackTrace.clone();
        for (int i = 0; i < defensiveCopy.length; i++) {
            if (defensiveCopy[i] == null)
                throw new NullPointerException("stackTrace[" + i + "]");
        }

        synchronized (this) {
            if (this.stackTrace == null && // Immutable stack
                backtrace == null) // Test for out of protocol state
                return;
            this.stackTrace = defensiveCopy;
        }
    }

    /**
     * Returns the number of elements in the stack trace (or 0 if the stack
     * trace is unavailable).
     *
     * package-protection for use by SharedSecrets.
     */
    native int getStackTraceDepth();

    /**
     * Returns the specified element of the stack trace.
     *
     * package-protection for use by SharedSecrets.
     *
     * @param index index of the element to return.
     * @throws IndexOutOfBoundsException if {@code index < 0 ||
     *         index >= getStackTraceDepth() }
     */
    native StackTraceElement getStackTraceElement(int index);


    private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        s.defaultReadObject();     // read in all fields
        if (suppressedExceptions != null) {
            List<Throwable> suppressed = null;
            if (suppressedExceptions.isEmpty()) {
                // Use the sentinel for a zero-length list
                suppressed = SUPPRESSED_SENTINEL;
            } else { // Copy Throwables to new list
                suppressed = new ArrayList<>(1);
                for (Throwable t : suppressedExceptions) {
                    // Enforce constraints on suppressed exceptions in
                    // case of corrupt or malicious stream.
                    if (t == null)
                        throw new NullPointerException(NULL_CAUSE_MESSAGE);
                    if (t == this)
                        throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE);
                    suppressed.add(t);
                }
            }
            suppressedExceptions = suppressed;
        } // else a null suppressedExceptions field remains null

        if (stackTrace != null) {
            if (stackTrace.length == 0) {
                stackTrace = UNASSIGNED_STACK.clone();
            }  else if (stackTrace.length == 1 &&
                        // Check for the marker of an immutable stack trace
                        SentinelHolder.STACK_TRACE_ELEMENT_SENTINEL.equals(stackTrace[0])) {
                stackTrace = null;
            } else { // Verify stack trace elements are non-null.
                for(StackTraceElement ste : stackTrace) {
                    if (ste == null)
                        throw new NullPointerException("null StackTraceElement in serial stream. ");
                }
            }
        } else {
            // A null stackTrace field in the serial form can result
            // from an exception serialized without that field in
            // older JDK releases; treat such exceptions as having
            // empty stack traces.
            stackTrace = UNASSIGNED_STACK.clone();
        }
    }

    /**
     *需要将对象进行序列化操作才能输出:
     *在Throwable类中使用输出流来进行输出,并把其对象作为输出流对象,
     *这就需要将对象进行序列化操作才能输出
     */
    private synchronized void writeObject(ObjectOutputStream s)
        throws IOException {
        // 确保stackTrace字段初始化为a
        // non-null value, if appropriate.  As of JDK 7, a null stack
        // trace field is a valid value indicating the stack trace
        // should not be set.
        getOurStackTrace();

        StackTraceElement[] oldStackTrace = stackTrace;
        try {
            if (stackTrace == null)
                stackTrace = SentinelHolder.STACK_TRACE_SENTINEL;
            s.defaultWriteObject();
        } finally {
            stackTrace = oldStackTrace;
        }
    }

    /**
     *
     * @param exception the exception to be added to the list of
     *        suppressed exceptions
     * @throws IllegalArgumentException if {@code exception} is this
     *         throwable; a throwable cannot suppress itself.
     * @throws NullPointerException if {@code exception} is {@code null}
     * @since 1.7
     */
    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);
    }

    private static final Throwable[] EMPTY_THROWABLE_ARRAY = new Throwable[0];

    /**
     *getSuppressed方法返回了被抑制(可能是try住的)的所有的异常信息
     */
    public final synchronized Throwable[] getSuppressed() {
        if (suppressedExceptions == SUPPRESSED_SENTINEL ||
            suppressedExceptions == null)
            return EMPTY_THROWABLE_ARRAY;
        else
            return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
    }
}

Throwable.fillInStackTrace()

这次源码追踪从最顶层的子类ArithmeticException到RuntimeException再到Exception最后到Throwable所有Errors和Exceptions的superclass,追到Throwable后观察其的所有构造函数,所有的构造函数中,首先第一件事是去填充栈追踪fillInStackTrace(),查看其实现,没料到居然在方法体里调用

private native Throwable fillInStackTrace(int dummy);


public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}
好吧,底层采用的是JNI(Java Native Interface),是通过其它语言(C/C++)来实现的,这个迟点又可以续一波博客了。。。

这里我们探究一下fillInStackTrace()的用法,上个实例先

package Exception;

public class ThrowsException {
    Throwable th = new Throwable();

    public ThrowsException() {
        System.out.println("ThrowsException无参构造函数");
    }

    public void object_1() {
        object_3();
    }

    public void object_2() {
        System.out.println("2。。。。start");
        //System.out.println(10/0);
        th.fillInStackTrace();
        th.printStackTrace(System.out);
        System.out.println("2。。。。end");
    }

    public void object_3() {
        object_2();
        th.fillInStackTrace();
        System.out.println("3。。。start");
        th.printStackTrace(System.out);
        System.out.println("3。。。end");
    }


    public static void main(String[] args) {
//      Error error = new Error("Throws a error message");
//      System.err.println(error.toString());
        new ThrowsException().object_1();

    }
}

显示结果:

ThrowsException无参构造函数
2。。。。start
java.lang.Throwable
    at Exception.ThrowsException.object_2(ThrowsException.java:17)
    at Exception.ThrowsException.object_3(ThrowsException.java:23)
    at Exception.ThrowsException.object_1(ThrowsException.java:11)
    at Exception.ThrowsException.main(ThrowsException.java:35)
2。。。。end
3。。。start
java.lang.Throwable
    at Exception.ThrowsException.object_3(ThrowsException.java:24)
    at Exception.ThrowsException.object_1(ThrowsException.java:11)
    at Exception.ThrowsException.main(ThrowsException.java:35)
3。。。end

再把object_2()中的//System.out.println(10/0);注释去掉

public void object_2() {
    System.out.println("2。。。。start");
    //System.out.println(10/0);
    th.fillInStackTrace();
    th.printStackTrace(System.out);
    System.out.println("2。。。。end");
}

显示结果

ThrowsException无参构造函数
Exception in thread "main" 2。。。。start
java.lang.ArithmeticException: / by zero
    at Exception.ThrowsException.object_2(ThrowsException.java:16)
    at Exception.ThrowsException.object_3(ThrowsException.java:23)
    at Exception.ThrowsException.object_1(ThrowsException.java:11)
    at Exception.ThrowsException.main(ThrowsException.java:35)

结论:fillInStackTrace()每次执行的时候都会清空栈内的trace信息,然后在当前调用的位置(抛出异常位置)重新建立trace

finally的使用

一、

    try {
        System.exit(0); 
    } catch (Exception e) {
        e.printStackTrace();
    }finally{
        System.out.println("触发finally");
    }

上面片段想要表达的是finally也有不执行的时候,那就是JVM被退出了

二、

try(Resource res = ...)
{
    //do some work
}

带资源的try块,在javase7中新增的方法,在这种写法下,try快退出时res会自动调用res.close(),这种写法仅仅在是“close”方法的时候可用。

三、finally导致的异常丢失问题

public static void main(String[] args) {    
        try {
            throw new RuntimeException();
        } finally{
//            return;
            int i = 1;
            int j = 0;
            int k = i/j;            
        }

    }

try块中捕获到了异常,然后在finally中也捕获到了异常,try块的异常会被覆盖,这里是Java异常设计的缺陷,但是当你在finally返回 “return;”时发现控制台什么输出的信息都没有,亿脸懵逼,因此建议在设计程序的时候尽量少把逻辑性代码放进finally内

异常处理的两种方式

https://blog.csdn.net/dengminghli/article/details/71598636


猜你喜欢

转载自blog.csdn.net/QQB67G8COM/article/details/81437156