今天同事上线上看日志,发现一堆只打印了”java.lang.NullPointerException”的异常,代码里面确实捕获且调用了输出堆栈信息的方法,日志里面却没有堆栈信息,甚是困惑。另一个同事说他之前遇到过,好像是JIT优化的结果。搜了一下,找到这样一篇文章(http://jawspeak.com/2010/05/26/hotspot-caused-exceptions-to-lose-their-stack-traces-in-production-and-the-fix/)和代码:
public class NpeThief {
public void callManyNPEInLoop() {
for (int i = 0; i < 100000; i++) {
try {
((Object)null).getClass();
} catch (Exception e) {
// This will switch from 2 to 0 (indicating our problem is happening)
System.out.println(e.getStackTrace().length);
}
}
}
public static void main(String ... args) {
NpeThief thief = new NpeThief();
thief.callManyNPEInLoop();
}
}
以下为启动参数时,在4W+次的时候就开始不打印堆栈信息了:
-Xcomp -server
-Xcomp表示纯编译执行(-Xcomp):所有方法在第一次调用的时候就开始执行编译,会导致启动速度慢。
相对应的是分层编译(-XX:+TieredCompilation): 分别是client启动时的c1编译器和server启动时的c2编译器。这2个编译器的目标不同。c2编译需要大量的统计信息,且编译慢,但是编译完的代码执行效率高。C1编译需要的信息相对少,但是编译快,效率相对低。分层编译就是一个折中,在系统启动的初期,用c1编译以便尽快进入编译执行。然后随着时间执行,大量的统计信息之后,再利用C2编译,以达到性能最大化。
在文章里面看到这样一个参数
-XX:-OmitStackTraceInFastThrow
用该参数作为关键字搜了一下hotspot源码,发现以下这段关键代码:
if (treat_throw_as_hot
&& (!StackTraceInThrowable || OmitStackTraceInFastThrow)) {
ciInstance* ex_obj = NULL;
switch (reason) {
case Deoptimization::Reason_null_check:
ex_obj = env()->NullPointerException_instance();
break;
case Deoptimization::Reason_div0_check:
ex_obj = env()->ArithmeticException_instance();
break;
case Deoptimization::Reason_range_check:
ex_obj = env()->ArrayIndexOutOfBoundsException_instance();
break;
case Deoptimization::Reason_class_check:
if (java_bc() == Bytecodes::_aastore) {
ex_obj = env()->ArrayStoreException_instance();
} else {
ex_obj = env()->ClassCastException_instance();
}
break;
}
可以看到,当开启参数OmitStackTraceInFastThrow(默认值就是true),NullPointerException,ArithmeticException,ArrayIndexOutOfBoundsException,ArrayStoreException,ClassCastException这5种异常在经过JIT优化后都不会输出堆栈信息。主要是空指针异常,除0的异常,数组越界异常,数组存储异常和类型转换异常。
感觉这种情况应该很少见,开启编译模式,都在调用了4W+后才发生。若只是分层编译,在10W+才出现。先记录一下。JIT这块也没有什么深入的理解。要关闭这个也很简单,-XX:-OmitStackTraceInFastThrow即可。
最后附上R大的一篇回答链接
https://www.zhihu.com/question/21405047