性能微优化-Exception须知
6.9 Exception须知
抛出异常在Java中是有相当大的代价,运行TryCatchTest测试了通过异常返回结果和正常返回结果码俩种情况的性能
@Benchmark
public boolean catchException(){
try{
business(status);
return true;
}catch(Exception ex){
return false;
}
}
@Benchmark
public boolean errorCode(){
int retCode = businessWitErrorCode(status);
return retCode==SUCCESS;
}
protected void business(int input){
if(input==0){
throw new IllegalArgumentException("模拟业务抛出异常");
}
//模拟正常
return ;
}
protected int businessWitErrorCode(int input){
if(input==0){
return FAILURE;
}
//模拟正常
return SUCCESS;
}通过JMH,会有如下输出
Benchmark Mode Score Units
c.i.c.c.TryCatchTest.catchException thrpt 593.377 ops/ms
c.i.c.c.TryCatchTest.errorCode thrpt 997708.623 ops/ms可以看到,通过返回异常码比抛出一个异常性能高处4个数量级,因此我们应该避免把正常的返回错误结果使用异常来代替。
之所以抛异常大致性能降低,是因为Java代码构造异常对象需要一个填写异常栈得操作,在Throwable类里,有一个方法
public synchronized Throwable fillInStackTrace() {
if (stackTrace != null ||
backtrace != null /* Out of protocol state */ ) {
fillInStackTrace(0);
stackTrace = UNASSIGNED_STACK;
}
return this;
}fillInStackTrace是个native方法,会填写异常栈。可想而知,这是一个异常耗时的操作,优化办法是可以自定义一个异常,重载fillInStackTrace方法,不执行fillInStackTrace操作
public class LightException extends RuntimeException{
public LightException(String msg){
super(msg);
}
public synchronized Throwable fillInStackTrace() {
this.setStackTrace(new StackTraceElement[0]);
return this;
}
}使用LightException代替IllegalArgumentException,性能有了明显改善,提高了俩个数量级
Benchmark Mode Score Units
c.i.c.c.TryCatchTest.catchException thrpt 38174.441 ops/ms
c.i.c.c.TryCatchTest.errorCode thrpt 1049694.073 ops/ms默认情况下,虚拟机会对某个方法频繁的抛出某些异常做了Fast Throw优化,如果检测到在代码里某个位置连续多次抛出同一类型异常的话,会决定用Fast Throw方式来抛出异常,而异常Trace即详细的异常栈信息不会被填写。这种异常抛出速度非常快,因为不需要在堆里分配内存,也不需要构造完整的异常栈信息,如下异常会使用Fast Throw优化
- NullPointerException
- ArithmeticException
- ArrayIndexOutOfBoundsException
- ArrayStoreException
- ClassCastException
这种优化固然提高了系统性能,但会导致异常栈消失,从而无法快速定位到错误代码,你不得不找到更早的日志文件(也许已经被压缩处理了),看看是否包含最初的异常栈,曾经一个线上系统因为这种空指针异常栈消失而花了一个小时解决问题,损失巨大。
避免这种异常栈优化,可以通过虚拟机参数-XX:-OmitStackTraceInFastThrow来忽略异常优化
