异常堆栈缺失与OmitStackTraceInFastThrow

目录

现象

异常没有堆栈信息。只有短短的异常类信息,例如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的实现中,首先会检查是否满足以下两个条件:

  1. 异常类型相同;
  2. 抛出异常的次数超过了阈值。

如果满足条件,就会跳过异常栈信息的构造,直接抛出异常。否则,就会正常抛出异常,并保留异常的栈信息。

这个实现的原理就是通过优化异常的抛出方式,提高性能。在异常被频繁抛出的情况下,OmitStackTraceInFastThrow可以有效地减少不必要的内存分配和栈信息构造,从而提高程序的运行效率。

阈值是多少

OmitStackTraceInFastThrow的阈值是由JVM参数-XX:FastUnwindLimit来控制的,默认值为4。这个参数表示在抛出异常之前,可以连续执行的字节码指令的数量。当超过这个阈值时,才会构造异常的详细栈信息。

源码

源代码

github地址

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的实例。

这段代码主要用于优化异常处理,特别是对于频繁发生的特定异常,通过一些特定的优化策略来减少处理成本。例如,对于某些特定的异常,会预先创建好对应的异常对象,当发生这种异常时,直接使用预先创建好的对象,避免创建新的对象带来的开销。同时,还可以避免在每次抛出异常时都需要生成和记录堆栈跟踪信息。

相关推荐
考虑考虑7 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261357 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊8 小时前
Java学习第22天 - 云原生与容器化
java
佛祖让我来巡山9 小时前
深入理解JVM内存分配机制:大对象处理、年龄判定与空间担保
jvm·内存分配·大对象处理·空间担保·年龄判定
渣哥10 小时前
原来 Java 里线程安全集合有这么多种
java
间彧10 小时前
Spring Boot集成Spring Security完整指南
java
间彧10 小时前
Spring Secutiy基本原理及工作流程
java
Java水解11 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆14 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学14 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端