异常堆栈缺失与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的实例。

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

相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity6 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天6 小时前
java的threadlocal为何内存泄漏
java
caridle7 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^7 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋37 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx