前言
在一次日志查 bug 时发现很多空指针异常没有堆栈信息,于是乎有了这篇文章。
代码
package xianzhan.jdk8;
public class Main {
public static void main(String[] args) {
String s = null;
s.toString();
}
}
执行上述代码后控制台输出:
可能有朋友就要问了,这不就是很清楚吗,空指针异常呀,堆栈信息都提示你哪行有问题了,这还用说什么呢?如果是这么简单,到也无需说什么,接下来再看看下面这代码:
package xianzhan.jdk8;
public class Main {
public static void main(String[] args) {
int i = 0;
for (; ; ) {
try {
i++;
String s = null;
s.toString();
} catch (Exception e) {
e.printStackTrace();
StackTraceElement[] stackTrace = e.getStackTrace();
if (stackTrace.length == 0) {
System.out.println("空指针异常次数:" + i);
break;
}
}
}
}
}
控制台输出:
和前面代码差不多,多了 for
循环和 try catch
,只不过输出到控制台的最后面,堆栈信息消失了,只剩下 java.lang.NullPointerException
这么一句信息。也就是说,当某个地方重复抛出一定次数 NullPointException
,那么 JVM 可能就会丢弃该处的堆栈信息。
探究
是什么导致了后面堆栈信息没了呢?带着这样的问题查找资料,最终在 NullPointerException in Java with no StackTrace 找到答案。
首先,我们先看下导致这个问题的代码是怎样的,查看 graphKit.cpp 文件的 GraphKit::builtin_throw(Deoptimization::DeoptReason reason, Node* arg)
方法:
//------------------------------builtin_throw----------------------------------
void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, Node* arg) {
bool must_throw = true;
// If this particular condition has not yet happened at this
// bytecode, then use the uncommon trap mechanism, and allow for
// a future recompilation if several traps occur here.
// If the throw is hot, try to use a more complicated inline mechanism
// which keeps execution inside the compiled code.
bool treat_throw_as_hot = false;
ciMethodData* md = method()->method_data();
if (ProfileTraps) {
if (too_many_traps(reason)) {
treat_throw_as_hot = true;
}
// (If there is no MDO at all, assume it is early in
// execution, and that any deopts are part of the
// startup transient, and don't need to be remembered.)
// Also, if there is a local exception handler, treat all throws
// as hot if there has been at least one in this method.
if (C->trap_count(reason) != 0
&& method()->method_data()->trap_count(reason) != 0
&& has_ex_handler()) {
treat_throw_as_hot = true;
}
}
// If this throw happens frequently, an uncommon trap might cause
// a performance pothole. If there is a local exception handler,
// and if this particular bytecode appears to be deoptimizing often,
// let us handle the throw inline, with a preconstructed instance.
// Note: If the deopt count has blown up, the uncommon trap
// runtime is going to flush this nmethod, not matter what.
if (treat_throw_as_hot
&& (!StackTraceInThrowable || OmitStackTraceInFastThrow)) {
// If the throw is local, we use a pre-existing instance and
// punt on the backtrace. This would lead to a missing backtrace
// (a repeat of 4292742) if the backtrace object is ever asked
// for its backtrace.
// Fixing this remaining case of 4292742 requires some flavor of
// escape analysis. Leave that for the future.
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;
default:
break;
}
if (failing()) { stop(); return; } // exception allocation might fail
if (ex_obj != NULL) {
if (env()->jvmti_can_post_on_exceptions()) {
// check if we must post exception events, take uncommon trap if so
uncommon_trap_if_should_post_on_exceptions(reason, must_throw);
// here if should_post_on_exceptions is false
// continue on with the normal codegen
}
// Cheat with a preallocated exception object.
if (C->log() != NULL)
C->log()->elem("hot_throw preallocated='1' reason='%s'",
Deoptimization::trap_reason_name(reason));
const TypeInstPtr* ex_con = TypeInstPtr::make(ex_obj);
Node* ex_node = _gvn.transform(ConNode::make(ex_con));
// Clear the detail message of the preallocated exception object.
// Weblogic sometimes mutates the detail message of exceptions
// using reflection.
int offset = java_lang_Throwable::get_detailMessage_offset();
const TypePtr* adr_typ = ex_con->add_offset(offset);
Node *adr = basic_plus_adr(ex_node, ex_node, offset);
const TypeOopPtr* val_type = TypeOopPtr::make_from_klass(env()->String_klass());
Node *store = access_store_at(ex_node, adr, adr_typ, null(), val_type, T_OBJECT, IN_HEAP);
add_exception_state(make_exception_state(ex_node));
return;
}
}
// %%% Maybe add entry to OptoRuntime which directly throws the exc.?
// It won't be much cheaper than bailing to the interp., since we'll
// have to pass up all the debug-info, and the runtime will have to
// create the stack trace.
// Usual case: Bail to interpreter.
// Reserve the right to recompile if we haven't seen anything yet.
ciMethod* m = Deoptimization::reason_is_speculate(reason) ? C->method() : NULL;
Deoptimization::DeoptAction action = Deoptimization::Action_maybe_recompile;
if (treat_throw_as_hot
&& (method()->method_data()->trap_recompiled_at(bci(), m)
|| C->too_many_traps(reason))) {
// We cannot afford to take more traps here. Suffer in the interpreter.
if (C->log() != NULL)
C->log()->elem("hot_throw preallocated='0' reason='%s' mcount='%d'",
Deoptimization::trap_reason_name(reason),
C->trap_count(reason));
action = Deoptimization::Action_none;
}
// "must_throw" prunes the JVM state to include only the stack, if there
// are no local exception handlers. This should cut down on register
// allocation time and code size, by drastically reducing the number
// of in-edges on the call to the uncommon trap.
uncommon_trap(reason, action, (ciKlass*)NULL, (char*)NULL, must_throw);
}
从上面代码可以知道,JVM 对特定异常做了优化:
NullPointerException
ArithmeticException
ArrayIndexOutOfBoundsException
ArrayStoreException
ClassCastException
当 JVM 检测到某个位置连续多次抛出同一个异常,到达某个数量的时候,就会抛出 JVM 内的一个默认的异常,该异常是 env()->NullPointerException_instance();
指向的数据,无需堆栈,因此速度会很快,也就是说,该操作是为了优化性能而做的一个优化。
OmitStackTraceInFastThrow
上面的优化是因为在启动服务时,JVM 默认添加了 -XX:+OmitStackTraceInFastThrow
参数,我们可以使用 java -XX:+PrintFlagsFinal
查看是否添加。