Java Throwable 源码解析

成员变量:

//空数组
private static final StackTraceElement[] UNASSIGNED_STACK = new StackTraceElement[0]
// 堆栈信息
private StackTraceElement[] stackTrace = UNASSIGNED_STACK;
// 异常的具体信息,比如:FileNotFoundException , 就是 the file that could not be found
private String detailMessage;
//导致这个异常被抛出的原因
private Throwable cause = this;

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

StackTraceElement 为程序的堆栈信息,描述了类,方法,文件名,行号。

下面代码为打印堆栈信息:

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.hadluo.test.exception.Main.b(Main.java:24)
com.hadluo.test.exception.Main.main(Main.java:20)


构造函数:

    public Throwable(String message, Throwable cause) {
        //填充执行堆栈跟踪
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }

fillInStackTrace

填充执行堆栈跟踪,记录当前JVM线程堆栈帧的状态信息(类,方法,文件名,行号)到stackTrace 变量中。

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

fillInStackTrace 方法时加锁的,并且会执行native方法,因此多线程环境下性能很差。我们有时new自己定义的业务异常时,且用不到堆栈,但是会调用这个方法,严重影响系统性能。下面看个实例:

public class Main {
	static class BusinessException extends RuntimeException {
		private static final long serialVersionUID = 1L;
	}
	static class BusinessException2 extends RuntimeException {
		private static final long serialVersionUID = 1L;

		@Override
		public synchronized Throwable fillInStackTrace() {
			return this;
		}
	}
	public static void main(String[] args) throws InterruptedException {
		ExecutorService service = Executors.newFixedThreadPool(100);
		int count = 5000000;
		long s = System.currentTimeMillis();
		CountDownLatch countDownLatch = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			service.execute(() -> {
				BusinessException b = new BusinessException();
				countDownLatch.countDown();
			});
		}
		countDownLatch.await();
		System.out.println("没有重写fillInStackTrace() 耗时:" + (System.currentTimeMillis() - s));

		s = System.currentTimeMillis();
		CountDownLatch countDownLatch2 = new CountDownLatch(count);
		for (int i = 0; i < count; i++) {
			service.execute(() -> {
				BusinessException2 b = new BusinessException2();
				countDownLatch2.countDown();
			});
		}
		countDownLatch2.await();
		System.out.println("重写fillInStackTrace() 耗时:" + (System.currentTimeMillis() - s));

	}
}

100个线程跑 5000000 次 构造BusinessException , 一个是没重写fillInStackTrace的,一个是重写了的。结果如下图:

https://pic3.zhimg.com/v2-de5da5e693e6a21c6ef2986747767976_b.jpg

这个差别还是很大的,下次主要自定义的异常一定要把fillInStackTrace 重写掉!!!!


initCause方法

设置异常的原因。这个方法最多只能被调用一次。 它通常从构造函数内部调用,或者在创建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;
    }

用法:

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


printStackTrace方法

将此throwable及其回溯信息打印到标准错误流。

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

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

    private void printStackTrace(PrintStreamOrWriter s) {
        Set<Throwable> dejaVu =
            Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        // 第一行为当前异常的toString
        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 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;
    }

可以看到先 getOurStackTrace 获取线程堆栈stackTrace 打印,然后再打印getSuppressed 和 getCause 。


强烈推荐一个 进阶 JAVA架构师 的博客

JAVA架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者