目录
现象
异常没有堆栈信息。只有短短的异常类信息,例如java.lang.NullPointerException。
完整的异常堆栈示例:
java.lang.NullPointerException
at fileop.ExceptionTest.testF(ExceptionTest.java:21)
at fileop.ExceptionTest.testB(ExceptionTest.java:38)
at fileop.ExceptionTest.lambda$main$0(ExceptionTest.java:66)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
原因
JDK5中引入了-XX:-OmitStackTraceInFastThrow这个参数,可以关闭掉该行为,具体可以看release note.
The compiler in the server VM now provides correct stack backtraces
for all "cold" built-in exceptions. For performance purposes, when
such an exception is thrown a few times, the method may be recompiled.
After recompilation, the compiler may choose a faster tactic using
preallocated exceptions that do not provide a stack trace. To disable
completely the use of preallocated exceptions, use this new flag:
-XX:-OmitStackTraceInFastThrow.
OmitStackTraceInFastThrow
OmitStackTraceInFastThrow是JVM的一个参数,它的作用是省略异常栈信息以快速抛出异常。当检测到在代码中连续多次抛出同一类型的异常时,JVM会使用Fast
Throw方式来抛出异常,异常的详细栈信息会被清空。这种异常抛出方式非常快,因为它不需要在堆里分配内存,也不需要构造完整的异常栈信息。
这个参数的优化对于提高性能非常有用,特别是在频繁抛出同一类型异常的情况下。这个优化在JIT编译时开启,并且在使用-server模式时,该优化选项是默认开启的。
源码层面分析OmitStackTraceInFastThrow
源码层面的OmitStackTraceInFastThrow的实现涉及到了JVM的核心部分,具体的位置在HotSpot虚拟机中的src/share/vm/interpreter/InterpreterRuntime.cpp文件中。
当一个异常被抛出时,JVM会调用InterpreterRuntime::throw_exception()函数,该函数会检查异常是否已经被抛出过,如果是,则调用OmitStackTraceInFastThrow。
在OmitStackTraceInFastThrow的实现中,首先会检查是否满足以下两个条件:
- 异常类型相同;
- 抛出异常的次数超过了阈值。
如果满足条件,就会跳过异常栈信息的构造,直接抛出异常。否则,就会正常抛出异常,并保留异常的栈信息。
这个实现的原理就是通过优化异常的抛出方式,提高性能。在异常被频繁抛出的情况下,OmitStackTraceInFastThrow可以有效地减少不必要的内存分配和栈信息构造,从而提高程序的运行效率。
阈值是多少
OmitStackTraceInFastThrow的阈值是由JVM参数-XX:FastUnwindLimit来控制的,默认值为4。这个参数表示在抛出异常之前,可以连续执行的字节码指令的数量。当超过这个阈值时,才会构造异常的详细栈信息。
源码
源代码
cpp
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;
}
解释
这段代码是Java虚拟机(JVM)的一部分,更具体地说,是GraphKit类中的builtin_throw函数。这个函数是用来处理内置的异常抛出的。
首先,函数接受两个参数:一个表示异常原因的枚举值(Deoptimization::DeoptReason
reason),另一个是要抛出的异常参数(Node* arg)。
然后,函数检查是否需要抛出异常。它首先检查是否启用了ProfileTraps,如果启用了,并且对于特定的异常原因,抛出的陷阱计数达到了某个阈值,或者存在异常处理程序,那么就会将treat_throw_as_hot设置为true。
接着,如果treat_throw_as_hot为true,并且满足特定的条件(在Throwable中没有堆栈跟踪或者启用了忽略堆栈跟踪的选项),那么就会使用预先创建的异常对象来抛出异常。具体使用哪个异常对象,取决于异常原因。例如,如果原因是null检查,那么就使用NullPointerException的实例。
这段代码主要用于优化异常处理,特别是对于频繁发生的特定异常,通过一些特定的优化策略来减少处理成本。例如,对于某些特定的异常,会预先创建好对应的异常对象,当发生这种异常时,直接使用预先创建好的对象,避免创建新的对象带来的开销。同时,还可以避免在每次抛出异常时都需要生成和记录堆栈跟踪信息。