JVM OmitStackTraceInFastThrow

1. 看看效果

看option名字能猜出一二:对于fast throw的异常,即hot exception,省略它的stacktrace。要想看到它的效果也很简单:

public class TestOmitStackTrace {
    public static void main(String...args){
        for(int i=0;i<50000;i++){
            try{
                new Object[]{null}[0].hashCode();
            }catch(Throwable npe){
                // swallow
                npe.printStackTrace();
            }
        }
    }
}
// -XX:+OmitStackTraceInFastThrow -XX:-TieredCompilation -XX:TieredStopAtLevel=4 c2only

最开始会输出stacktrace,告诉哪里npe了(note:这里npe和常规的不太一样,因为测试用的JDK17,它引入了JEP 358 Helpful NullPointerException),后面只有npe,没有stacktrace:

...
java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "<local2>" is null
	at TestOmitStackTrace.main(TestOmitStackTrace.java:6)
java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "<local2>" is null
	at TestOmitStackTrace.main(TestOmitStackTrace.java:6)
java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "<local2>" is null
	at TestOmitStackTrace.main(TestOmitStackTrace.java:6)
java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "<local2>" is null
	at TestOmitStackTrace.main(TestOmitStackTrace.java:6)
java.lang.NullPointerException
java.lang.NullPointerException
java.lang.NullPointerException
java.lang.NullPointerException
java.lang.NullPointerException
java.lang.NullPointerException
....

这就是所谓的 省略hot exception的stacktrace,关闭-XX:-OmitStackTraceInFastThrow恢复正常,所有异常都会输出stacktrace。

2. hot exception判定

OmitStackTraceInFastThrow是C2的一个优化,位于GraphKit::builtin_throw(),它可以省略hot exception的stacktrace。那么,怎么判断一个异常是不是hot呢?

//------------------------------builtin_throw----------------------------------
void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, Node* arg) {
  ...
  bool treat_throw_as_hot = false;
  ciMethodData* md = method()->method_data();

  if (ProfileTraps) {
    // 如果当前字节码触发了太多的trap,那就判定为hot exception
    if (too_many_traps(reason)) {
      treat_throw_as_hot = true;
    }
    // 如果有一个local exception handler,那么就把所有异常视为hot exception
    if (C->trap_count(reason) != 0
        && method()->method_data()->trap_count(reason) != 0
        && has_ex_handler()) {
        treat_throw_as_hot = true;
    }
  }
  // 到这里,该异常是否hot已经判定,剩下的内容会在后面讨论
  if (treat_throw_as_hot
      && (!StackTraceInThrowable || OmitStackTraceInFastThrow)) {
    ...
  }
  ...
}

只要符合上面的hot exception就可以吗?

把TestOmitStackTrace里面的new Object[]{null}[0].hashCode()改成throw new Exception() 试试,你会发现不会省略stacktrace,因为除了上面的hot exception之外,还要求异常为特定类型,如果不是这些类型的异常就不能省略stacktrace:

//------------------------------builtin_throw----------------------------------
void GraphKit::builtin_throw(Deoptimization::DeoptReason reason, Node* arg) {
  ...
  if (treat_throw_as_hot
      && (!StackTraceInThrowable || OmitStackTraceInFastThrow)) {
    // 只有下面这些特定类型的异常,才能省略stacktrace。
    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 (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));

      // 将ex_obj的detailedMessage字段置null
      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;
    }
  }

  // 如果不是上面那些特定类型的异常,就回退到解释器,正常抛出,不省略stacktrace
  ...
}

如果是hot exception,并且开启了OmitStackTraceInFastThrow,C2会走到上面的逻辑。它检查该异常是否支持省略stacktrace,在之前的例子中,是NPE异常,支持省略,因此ex_obj复用提前分配好的 env()->NullPointerException_instance(),省略了new一个NPE的开销。

接下来c2找到异常对象的detailedMessage字段(对象基址+字段偏移),这一步会产生一个AddPNode,即指针加法节点adr,再将null赋值给detailedMessage字段,这一步产生Store节点。生成的idealgraph如下:
JVM OmitStackTraceInFastThrow

StoreN#221相当于赋值,左值是AddP#87(throwable_base+detailmessage_offset),右值是ConN#6(null)。至于后面的MergeMem#257的出现是因为idealgraph将内存状态看作整体,对象字段读写操作实际上是对这个整体其中的一个指定内存切片进行操作,会产生新的内存切片MergeMem#257。

上一篇:深入浅出Blazor webassembly之程序配置


下一篇:《经典力扣题》二叉树基础题(一)