Java异常(exception)性能优化

2018-09-11  本文已影响0人  司青玄

在Java中,构造异常对象是"十分"耗时的,其原因是在默认情况下,创建异常对象时会调用父类ThrowablefillInStackTrace()方法生成栈追踪信息,JDK中的源码如下:

public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0); // native方法
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

在我自己做的测试中,new一个带有栈追踪信息的Exception对象要比创建不带追踪信息的对象慢50倍以上。虽然打印调用栈能够精确定位到错误发生的代码所在行,但我们可以考虑一下,真的有必要让所有异常都生成这些信息吗?

我们在开发业务系统的过程中一般都会使用异常机制来实现错误处理逻辑,这些异常通常都可以分成两大类:

其实对于业务异常,我们只需要简单的知道一个描述问题的字符串即可,栈追踪信息对我们的意义并不大。而对于系统异常,追踪信息才是排查错误不可或缺的参考。因此我们可以想办法控制一下,创建业务异常时不生成调用栈追踪信息以降低开销,系统异常则正常生成。

其实方法非常简单,在我们自定义异常时,只需要重写父类的一个带有4个参数的构造方法即可,此方法在ExceptionRuntimeException类中都存在:

protected RuntimeException(String message, Throwable cause,
                               boolean enableSuppression,
                               boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

这几个参数的意义如下:

例如,业务异常可以这样定义:

public class XXXException extends RuntimeException {
    /**
     * 仅包含message, 没有cause, 也不记录栈异常, 性能最高
     * @param msg
     */
    public XXXException(String msg) {
        this(msg, false);
    }

    /**
     * 包含message, 可指定是否记录异常
     * @param msg
     * @param recordStackTrace
     */
    public EngineException(String msg, boolean recordStackTrace) {
        super(msg, null, false, recordStackTrace);
    }

    /**
     * 包含message和cause, 会记录栈异常
     * @param msg
     * @param cause
     */
    public EngineException(String msg, Throwable cause) {
        super(msg, cause, false, true);
    }
}

即通过使用父类中4参数的构造方法精确控制异常类的行为。当我们想要创建"轻量级"异常时,使用第一个构造方法即可;如果我们想将系统级异常封装成一下,并希望在日志中打印栈追踪时,就使用第三个构造方法。

PS: 只有在高并发系统中做上述优化才会有明显效果。如果抛异常不频繁的话也不会有明显效果,因为即便是慢50倍,实际也是纳秒级的区别,对一个请求处理来说微不足道。

上一篇下一篇

猜你喜欢

热点阅读