抛异常的最大开销是异常栈的构建过程,如果你的程序调用很深,特别是用了第三方开源框架,这个开销是不容忽视的
开销在哪
查看jdk源码
/**
* Constructs a new throwable with the specified cause and a detail
* message of {@code (cause==null ? null : cause.toString())} (which
* typically contains the class and detail message of {@code cause}).
* This constructor is useful for throwables that are little more than
* wrappers for other throwables (for example, {@link
* java.security.PrivilegedActionException}).
*
* <p>The {@link #fillInStackTrace()} method is called to initialize
* the stack trace data in the newly created throwable.
*
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A {@code null} value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public Throwable(Throwable cause) {
fillInStackTrace();
detailMessage = (cause==null ? null : cause.toString());
this.cause = cause;
}
主要的性能瓶颈在fillInStackTrace,这是一个native方法.会构建整个异常栈. 方法签名如下
/**
* Fills in the execution stack trace. This method records within this
* {@code Throwable} object information about the current state of
* the stack frames for the current thread.
*
* <p>If the stack trace of this {@code Throwable} {@linkplain
* Throwable#Throwable(String, Throwable, boolean, boolean) is not
* writable}, calling this method has no effect.
*
* @return a reference to this {@code Throwable} instance.
* @see java.lang.Throwable#printStackTrace()
*/
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);
如何解决
- 创建异常类的时候重写fillInStackTrace方法.java7原生支持
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;
}
- 去掉异常.现在很多业务系统用异常实现程序正常业务逻辑.这个对性能影响比较大,尤其是并发比较大的时候.
寻找异常
有时候你无法知道那个异常抛的最多,有些三方包 自己throw Exception 但自己又catch住.
- brace 跟踪Exception <init> 对异常栈,汇总
- perf top去看下us的开销,如果_ZN19java_lang_Throwable19fill_in_stack_traceE6HandleP6Thread这个排名很靠前,那就好好检查下.
讨论
用异常实现正常的业务流程有以下优点
- 代码比较精炼.增强代码可读性.可以不用对服务方法设计统一的返回值
- 可以使用切面技术(拦截器 异常处理器) 对异常做统一的监控 和处理.
缺点:性能
改进:
- 不构建异常堆栈,而是保存失败原因FailCause或者错误码
- 重写fillInStackTrace方法进行noop
参考
知乎上有异常作为业务流程控制的讨论.请移步
https://www.zhihu.com/question/21405047