> 文章列表 > 栈上替换on-stack replacement(OSR)

栈上替换on-stack replacement(OSR)

栈上替换on-stack replacement(OSR)

文章目录

  • 介绍
  • 配置
  • 实现
  • osr_entry_bci()函数

介绍

  OSR 是一种 JIT 编译器中的技术,它的作用是在程序的运行过程中,将当前运行的代码从解释执行状态转化为本地机器码执行状态。通常情况下,JIT 编译器在解释执行代码时,会把经常执行的代码编译成本地机器指令,提高代码的性能。但是,有些情况下,程序的运行状态可能会发生变化,使得 JIT 编译器编译的代码已经无法满足当前的需求,这时候就需要使用 OSR 技术进行栈上替换。
  在 JIT 编译器中,OSR 技术主要用于两种情况:循环加速和异常处理。在循环加速中,JIT 编译器会将循环的迭代次数计算出来,当迭代次数达到一定值时,就会将解释执行的代码替换为本地机器指令,从而提高循环的执行效率。而在异常处理中,JIT 编译器会将异常处理代码编译成本地机器指令,当程序出现异常时,就会使用 OSR 技术进行栈上替换,以加快异常处理的速度。
  OSR 技术的实现过程比较复杂,它需要对程序进行动态分析,找到需要进行栈上替换的代码段,并将解释执行的代码转化为本地机器指令。同时,在进行 OSR 替换时,还需要注意保持程序的状态,确保替换后程序能够正确执行。
  总的来说,JIT 编译器的 OSR 技术是一种非常重要的优化手段,它可以帮助程序在运行时动态优化,提高代码的性能和响应速度。在实际开发中,开发人员可以通过合理使用 OSR 技术,来优化程序的性能,提高用户的使用体验。

配置

  不同的 JIT 实现可能有不同的命令行选项来配置 OSR,下面我将介绍几个常见的 JIT 实现的命令行选项:
  HotSpot JVM:-XX:+PrintCompilation
  HotSpot JVM 是 Java 平台的默认 JIT 实现,它提供了 -XX:+PrintCompilation 命令行选项来打印 JIT 编译器的输出信息,其中包括 OSR 编译信息。通过这个选项,可以查看哪些方法被编译成了本地机器指令,以及哪些方法是通过 OSR 技术进行栈上替换的。
  LLVM:-O1/-O2/-O3
  LLVM 是一种基于模块化设计的编译器框架,它提供了多种优化级别的命令行选项,包括 -O1、-O2 和 -O3 等。在 JIT 编译时,可以通过指定不同的优化级别来控制 OSR 的使用情况。一般来说,较低的优化级别可以更容易地进行 OSR 替换。
  GraalVM:-J-XX:+UseOSR
  GraalVM 是一种基于 Java 虚拟机的全栈编译器,它提供了 -J-XX:+UseOSR 命令行选项来启用 OSR 技术。通过这个选项,可以让 GraalVM 在运行时使用 OSR 技术,对程序进行动态优化,提高代码的性能和响应速度。
  需要注意的是,不同的 JIT 实现可能会提供不同的命令行选项来配置 OSR,而且这些选项可能会因版本而异。因此,在使用 JIT 编译器时,建议查阅相关的文档和手册,了解具体的命令行选项和配置方法。

实现

  在 JVM 中,OSR 的实现主要涉及到两个部分:解释器和编译器。首先,解释器会在执行代码时,记录下当前的执行状态,包括方法栈帧、局部变量和操作数栈等信息。然后,在执行到 OSR 点时,解释器会将这些信息传递给编译器,以便编译器进行代码优化和本地机器代码生成。
  编译器主要有两个任务:第一个是生成本地机器代码,第二个是调整当前栈帧的状态,以便能够从解释执行状态转换为本地机器代码执行状态。具体实现过程如下:
  通过解释器传递过来的方法栈帧信息,编译器会重新构建一个本地机器代码栈帧,并在栈帧中添加必要的参数和状态信息。
  编译器会根据传递过来的局部变量和操作数栈信息,生成本地机器代码,并将本地机器代码插入到当前执行位置的前面。这样,当程序执行到 OSR 点时,就能够直接跳转到本地机器代码执行,而不需要再次解释执行。
  为了确保本地机器代码能够正确执行,编译器还需要调整当前栈帧的状态,以便能够从解释执行状态转换为本地机器代码执行状态。这一过程包括修改栈帧的指针、设置寄存器值和清空操作数栈等操作。
  最后,编译器会将编译好的本地机器代码保存到一个单独的 CodeBlob 中,并将其注册到 JVM 的 CodeCache 中,以便后续的执行。

osr_entry_bci()函数

  在 JVM 源码中,osr_entry_bci() 函数是 OSR 技术实现的核心函数之一。该函数的作用是确定当前 OSR 点在方法中的位置,以便 JIT 编译器能够生成正确的本地机器代码。
  osr_entry_bci() 函数定义在 HotSpot 源码的 methodOop.hpp 文件中,其实现比较简单,其大致逻辑如下:

  1. 首先,函数会检查当前方法是否已经被 JIT 编译过,如果没有被编译过,则返回 -1,表示当前方法不支持 OSR 技术。
  2. 如果当前方法已经被 JIT 编译过,则函数会查找该方法的编译信息,并检查是否存在 OSR 点。
  3. 如果存在 OSR 点,则函数会返回 OSR 点在方法中的字节码偏移量(bci),以便 JIT 编译器生成正确的本地机器代码。

  在 HotSpot 中,osr_entry_bci() 函数的具体实现比较复杂,涉及到多个模块和类的协作,下面我简要介绍一下其实现过程:

  1. 首先,函数会通过方法描述符(即 methodOopDesc 类对象)获取该方法的编译信息(即 CompileTask 类对象),其中包括代码缓存(CodeCache)和抽象语法树(AST)等信息。
  2. 接下来,函数会调用 CompileTask 类对象的 osr_nmethod() 方法,根据当前线程的上下文信息,找到方法中的 OSR 点。该方法的实现过程涉及到多个编译器模块和数据结构,包括 C1、C2 编译器、IR 抽象语法树等。
  3. 如果找到了 OSR 点,则函数会返回该点在方法中的字节码偏移量,以便 JIT 编译器生成正确的本地机器代码。