GeXiangDong

精通Java、SQL、Spring的拼写,擅长Linux、Windows的开关机

0%

java 不带堆栈信息的异常

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class NpeThief {
public void callManyNPEInLoop() {
int preLen = -1;
for (int i = 0; i < 10000000; i++) {
try {
((Object)null).getClass();
} catch (Exception e) {
if(e.getStackTrace().length != preLen){
System.out.println("#" + i + " " + e.getStackTrace().length);
preLen = e.getStackTrace().length;
}
}
}
}
public static void main(String[] args) {
NpeThief thief = new NpeThief();
thief.callManyNPEInLoop();
}
}

用java NpeThief运行,会输出2、0两行
用java -XX:-OmitStackTraceInFastThrow NpeTHief运行,则只会有2一行,也能直接感觉到Fast Throw优化带来的运行速度的提升。