Java的异常默认都有堆栈信息,详细显示出来调用层次,这对查找错误原因很有帮助。今日遇到一个无堆栈信息的java.lang.NullPointerException
经查询原来是HotSpot VM提供的一种叫fast throw的优化导致的。
Fast Throw
有些特定的隐式异常类型(NullPointerException、ArithmeticException( / 0)之类)如果在代码里某个特定位置被抛出过多次的话,HotSpot Server Compiler(C2)会透明的决定用fast throw来优化这个抛出异常的地方——直接抛出一个事先分配好的、类型匹配的异常对象。这个对象的message和stack trace都被清空。抛出这个异常的速度是非常快,不但不用额外分配内存,而且也不用爬栈;但反面就是可能正好是需要知道哪里出问题的时候看不到stack trace了。从Sun JDK5开始要避免C2做这个优化还得额外传个VM参数:-XX:-OmitStackTraceInFastThrow
。
既然这种异常速度比较快,那么我们自定义的一些业务异常,有些是不会使用堆栈信息的,是不是也可这样做呢?是的。可以通过继承Throwable,使用构造函数Throwable(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)来实现。
如果不是继承的Throwable,其它异常也有类似的protected方法,例如RuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
禁用 Fast Throw
ubuntu 16.x 可以修改/etc/systemd/system/tomcat 配置文件
1 | Environment=’CATALINA_OPTS=-Xms512M -Xmx2048M -server -XX:+UseParallelGC -XX:-OmitStackTraceInFastThrow‘ |
重现 Fast Throw
可以用下面的这段代码重现没有堆栈的异常:
1 | public class NpeThief { |
用java NpeThief运行,会输出2、0两行
用java -XX:-OmitStackTraceInFastThrow NpeTHief运行,则只会有2一行,也能直接感觉到Fast Throw优化带来的运行速度的提升。