Throwable源码
首先给大家看一段JDK的Throwable源码
上面这段JDK的源码就是抛出异常时会调用的方法,更准确的说是Throwable
的所有构造方法中都会调用fillInStackTrace
,由于所有的异常都直接或间接的继承了Throwable
,所以当我们所以当我们new一个异常的时候,一定会执行fillInStackTrace
(创建子类的时候会先执行父类的构造方法)
这个方法暴露出两个问题
- 使用了synchronized修饰了整个异常方法
- 需要追踪线程运行堆栈信息
异常种类
- 业务异常 这些是我们自定义的、可以预知的异常,抛出这种异常并不表示系统出了问题,而是正常业务逻辑上的需要,例如用户名密码错误、参数错误等。
- 系统异常 往往是运行时异常,比如数据库连接失败、IO异常、空指针等,这种异常产生多数表示系统存在问题,需要人工排查定为。
相信大家都接触过异常,对于业务异常,我们只需要简单的一个描数问题的字符串即可,堆栈追踪信息对我们的意义并不大。而对于系统异常,追踪信息才是排查错误不可或缺的参考。
大家试想,如果前端传的参数错了,系统里就抛出一个异常,那么在双十一的情况下一秒钟得抛出多少个异常呢?
那么有什么办法可以优化异常的性能吗?
答案是有的,很多牛逼的框架自定异常的时候都会覆写fillInStackTrace
,在方法内部直接return this
。
性能测试
- 创建1000000次普通的Java异常对象(CustomException1 extands Exception)
- 创建1000000次改进后的Java异常对象(CustomException2 extands Exception)
public class CustomException1 extends Exception {
}
public class CustomException2 extends Exception {
@Override
public Throwable fillInStackTrace() {
return this;
}
}
public class Test {
public static void main(String[] args) {
long l1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
new CustomException();
}
long l2 = System.currentTimeMillis();
System.out.println(l2 - l1);
for (int i = 0; i < 1000000; i++) {
new CustomException2();
}
long l3 = System.currentTimeMillis();
System.out.println(l3 - l2);
}
}
输出结果
可以看到创建一个普通Exception和覆写了 fillInStackTrace 的Exception
,性能差距了很多倍。
其实在Java1.7开始 Throwable 就新增了一个构造方法,供我们选择开启或关闭爬栈
从代码中可以看到,当writableStackTrace
这个参数为 false 的时候,就不会去调用 fillInStackTeace 方法了。
一般我们的业务异常都是继承自 RuntimeException ,同样也有一个构造方法可以供我们选择开启或关闭爬栈
问题思考
虽然覆写 fillInStackTrace 或在构造方法种关闭爬栈 可以极大的提高性能,但是我们一定要考虑清楚以下问题
- 自定义异常是否真的不需要堆栈信息
如果不需要,我个人是强烈推荐覆写掉的。