精华内容
下载资源
问答
  • 即时编译器
    千次阅读
    2022-03-22 22:02:58

    本文为《深入学习 JVM 系列》第十六篇文章

    我们在前文学习 Java 是如何执行的这篇文章中有提及即时编译器,这是一项用来提升应用程序运行效率的技术。通常而言,代码会先被 Java 虚拟机解释执行,之后反复执行的热点代码则会被即时编译成为机器码,直接运行在底层硬件之上。

    那么问题来了,既然在 HotSpot 中即时编译可以提升程序运行效率,为什么还需要解释器呢?

    解释器与编译器

    首先需要了解的是,并不是所有的 Java 虚拟机都采用解释器与编译器并存的运行架构, 但是主流的 Java 虚拟机,比如说 HotSpot 内部就同时包含解释器和编译器。注意此处的编译器指的是后端编译器,而 Javac 编译器属于前端编译器(前文有详细介绍),下文如无特殊介绍,都指的是后端编译器。

    至于为何解释器和编译器共存,是因为两者各有优势。

    1、当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即运行。

    2、当程序启动后,随着时间的推移,编译器逐渐发挥作用, 把越来越多的代码编译成本地代码,这样可以减少解释器的中间损耗,获得更高的执行效率。

    HotSpot 为了提升程序执行效率而选用即时编译的方法,同时,解释器还可以作为编译器激进优化时后备的“逃生门”。激进优化指的是让编译器根据概率选择一些不能保证所有情况都正确,但大多数时候都能提升运行速度的优化手段。当激进优化的假设不成立,比如加载了新类以后,类型继承结构出现变化、 出现“罕见陷阱”(Uncommon Trap)时可以通过逆优化(Deoptimization) 退回到解释状态继续执行。

    因此在整个 Java 虚拟机执行架构里,解释器与编译器经常是相辅相成地配合工作,如下图所示:

    HotSpot 虚拟机中内置了两个用 C++实现的 JIT compiler(即时编译器),分别被称为“客户端编译器”(Client Compiler) 和“服务端编译器”(Server Compiler) , 或者简称为 C1编译器和 C2编译器(部分资料和JDK源码中C2也叫Opto编译器)。上述两种编译器对应两种编译方式,我们简称为 C1编译和 C2编译,用户也可以使用“-client”或“-server”参数去强制指定虚拟机采用 C1编译或是 C2编译。

    关于上述两种编译方式,优势各异,C1的编译速度更快,C2的编译质量更高,相较于C1性能通常高 30%以上。

    在 JDK10 时引入了 Graal 编译器,用来代替 C2 编译器,目前还在试验阶段。Graal 是一个以 Java 为主要编程语言,面向 Java bytecode的编译器。与用 C++实现的C1及C2相比,它的模块化更加明显,也更加容易维护。Graal既可以作为动态编译器,在运行时编译热点方法;亦可以作为静态编译器,实现 AOT 编译。

    Graal 编译器可以通过 Java 虚拟机参数 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 启用。当启用时,它将替换掉HotSpot 中的 C2 编译器,并响应原本由 C2负责的编译请求。

    三种编译模式

    编译器是二选一,然后再与解释器搭配进行工作,这种方式在 HotSpot 中被称为“混合模式”(Mixed Mode)。 用户也可以使用参数“-Xint”强制虚拟机运行于“解释模式”(Interpreted Mode),全部代码都使用解释方式执行。相应地,用户也可以使用参数“-Xcomp”强制虚拟机运行于“编译模式”(Compiled Mode),这时候将优先采用编译方式执行程序,但是解释器仍然要在编译无法进行的情况下介入执行过程。

    我们测试如下一段代码:

    public class HotCodeTest {
    
      public static void main(String[] args) {
        nlp();
      }
    
      public static void nlp() {
        int sum = 0;
        long start = System.currentTimeMillis();
        for (int i = 0; i < 20000000; i++) {
          sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("cost:" + (end - start) + " ms");
      }
    
    }
    

    先使用 javac 编译器获得 class 文件,接着进行下面的步骤。

    1、只进行解释执行:-Xint

    // JVM参数为:-XX:+PrintCompilation -Xint
    //结果:
    cost:160 ms
    

    -Xint 参数会强制 JVM 解释执行所有的字节码,没有编译信息,当然这会降低运行速度,通常低10倍或更多。

    2、关闭解释器:-Xcomp

    // JVM参数为:-XX:+PrintCompilation -Xcomp
    //结果:
    cost:1 ms
    

    耗时极小,同时打印出了大量的编译信息,其中就包括 nlp方法的编译信息。JVM在第一次使用时会把所有的字节码编译成本地代码,从而带来最大程度的优化。看起来很不错,但正如文章开头所说的那样,如果程序需要迅速执行,则解释器效率更高。比如将上述代码的循环次数为 100,则可以发现解释执行的耗时小于编译执行。

    3、混合模式:-Xmixed

    // JVM参数为:-XX:+PrintCompilation
    //结果:
    cost:9 ms
    

    JDK8 之后 HotSpot 默认采用混合模式,即解释执行+JIT。结果打印出了大量的编译信息,其中就包括 nlp方法的编译信息。

    综上,也可以正面印证了解释器与编译器共存的必要性。

    分层编译

    由于即时编译器编译本地代码需要占用程序运行时间,通常要编译出优化程度越高的代码,所花费的时间便会越长;更有甚者,还需要解释器的帮助,解释器可能还要替编译器收集性能监控信息,这对解释执行阶段的速度也有所影响。因此为了在程序启动响应速度与运行效率之间达到最佳平衡,HotSpot 虚拟机在编译子系统中加入了分层编译(tiered compilation)的功能。

    分层编译的概念很早就被提出,但是知道 JDK6 才初步实现,JDK7 作为默认编译策略被开启。

    分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次,其中包括:

    level 0、Interpreter 解释执行,并且解释器不开启性能监控功能(Profiling)。

    level 1、C1 编译执行,不开启性能监控功能(Profiling)。

    level 2、C1 编译执行,开启方法调用次数以及循环回边执行次数等有限的性能监控功能(Profiling)。

    level 3、C1 编译执行,开启全部性能监控功能(Profiling)。除了第2层的统计信息外,还会收集如分支跳转、 虚方法调用版本等全部的统计信息。

    level 4、C2 编译执行。

    这里解释一下,profiling 是指在程序执行过程中,解释器收集能够反映程序执行状态的数据。这里所收集的数据我们称之为程序的 profile。

    以上层次并不是固定不变的, 根据不同的运行参数和版本,虚拟机可以调整分层的数量。各层次编译之间的交互、转换关系如下图所示:

    通常情况下,C2 代码的执行效率要比 C1代码的高出30%以上。C1层执行的代码,按执行效率排序从高至低则是1层>2层>3层。在 5 个层次的执行状态中,1 层和 4 层为终止状态。当一个方法被终止状态编译过后,如果编译后的代码并没有失效,那么 Java 虚拟机是不会再次发出该方法的编译请求的。

    关于上图介绍的四种编译途径,具体描述为:

    1. 图中的第一条路径,代表通常情况下,热点方法会被 3 层的 C1 编译,然后再被 4 层的 C2 编译。
    2. 如果方法的字节码数目比较少(如 getter/setter),而且 3 层的 profiling 没有可收集的数据。那么,Java 虚拟机断定该方法对于 C1 代码和 C2 代码的执行效率相同。在这种情况下,Java 虚拟机会在 3 层编译之后,直接选择用 1 层的 C1 编译。由于这是一个终止状态,因此 Java 虚拟机不会继续用 4 层的 C2 编译。
    3. 在 C1 忙碌的情况下,Java 虚拟机在解释执行过程中对程序进行 profiling,而后直接由 4 层的 C2 编译。
    4. 在 C2 忙碌的情况下,基于前文提到 C1中的执行效率是1层>2层>3层,方法会被 2 层的 C1 编译,然后再被 3 层的 C1 编译,以减少方法在 3 层的执行时间。

    Java 8 默认开启了分层编译。不管是开启还是关闭分层编译,原本用来选择即时编译器的参数 -client 和 -server 都是无效的。当关闭分层编译的情况下(-XX:-TieredCompilation命令用于关闭分层编译),Java 虚拟机将直接采用 C2。

    如果你希望只是用 C1,那么你可以在打开分层编译的情况下使用参数 -XX:TieredStopAtLevel=1。在这种情况下,Java 虚拟机会在解释执行之后直接由 1 层的 C1 进行编译。TieredStopAtLevel =4,则由4层的 C2 进行编译。

    即时编译

    即时编译建立在程序符合二八定律的假设上,也就是百分之二十的代码占据了百分之八十的计算资源。

    对于占据大部分的不常用的代码,我们无需耗费时间将其编译成机器码,而是采取解释执行的方式运行;另一方面,对于仅占据小部分的热点代码,我们则可以将其编译成机器码,下次可以重复调用,以达到理想的运行速度。

    即时编译的触发

    上文可知即时编译器编译的目标是“热点代码”,这里所指的热点代码主要有两类,包括:

    • 被多次调用的方法。
    • 被多次执行的循环体。

    根据“热点代码”的描述,那么到底执行多少次才算是热点代码?还有,虚拟机如何统计一个方法或一段代码被执行过多少次呢?

    判断一段代码是不是热点代码,是不是需要触发即时编译,这样的行为称为"热点探测",其实热点探测并不一定要知道方法具体被调用了多少次,目前主要的热点探测判定方式有两种:

    • 基于采样的热点探测
    • 基于计数器的热点探测

    HotSpot 虚拟机中使用的是第二种基于计数器的热点探测方法,它为每个方法准备了两类计数器:方法调用计数器回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。(谁来负责这个事情,还记得分层编译小节中提到的“解释器开启性能监控功能(Profiling)”吗,就是解释器来负责统计计数数据并进行校验)

    方法调用计数器

    顾名思义,这个计数器就是用于统计方法被调用的次数,它的默认阈值在 Client 模式下是1500次,在 Server 模式下是10000次。这个阈值可以通过参数-XX:CompileThreshold 来人为设定。当一个方法被调用时,会检查方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器和回边计数器值之和是否超过方法调用计数器的阈值。如果已经超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。

    如果这个参数不做任何设置,那么方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会少一半,这个过程称为方法的调用计数器热度的衰减,而这段时间就称为此方法统计的半衰周期。进行热度衰减的动作是在虚拟机进行垃圾回收时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay 来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalfLifeTime 参数设置半衰周期的时间,单位是秒。

    那如果参数不设置的话,执行引擎并不会同步等待编译请求完成,而是直接进入解释器按照解释方法执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会被系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。

    如下代码所示:

    // 参数:-XX:+PrintCompilation -XX:-TieredCompilation(关闭分层编译)
    private static Random random = new Random();
    
    public static void main(String[] args) {
      long sum = 0;
      long start = System.nanoTime();
      for (int i = 0; i < 12000; i++) {
        sum += getRandomNum();
      }
      long end = System.nanoTime();
      System.out.println(end - start);
    }
    
    public static int getRandomNum() {
      return random.nextInt(10);
    }
    

    在输出的编译结果中可以看到 getRandomNum 方法。关闭分层编译后,按照上文描述,编译器默认为 C2编译器,而此时的方法计数器阈值为 10000,虽然方法调用次数并不严格等于方法调用计数器统计的结果,但达到一定次数后,也算是热点代码,最终触发即时编译。

    回边计数器

    它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为"回边"(这里的次数指的是回边次数,而不是循环次数,因为并非所有的循环都是回边, 如空循环实际上就可以视为自己跳转到自己的过程,因此并不算作控制流向后跳转,也不会被回边计数器统计)。显然,建立回边技术其统计的目的就是为了触发OSR编译。关于回边计数器的阈值,虽然 HotSpot 也提供了一个类似于方法调用计数器阈值-XX:CompileThreshold 的参数-XX:BackEdgeThreshold 供用户设置,但是当前虚拟机实际上并未使用此参数,因此我们需要设置另外一个参数-XX:OnStackReplacePercentage 来间接调整回边计数器的阈值,其计算公式如下:

    (1)Client模式

    方法调用计数器阈值 × OSR比率 / 1000,其中OSR比率默认值933,如果都取默认值,Client模式下回边计数器的阈值应该是13995。

    (2)Server模式

    方法调用计数器阈值 × (OSR比率 - 解释器监控比率) / 100,其中OSR比率默认140,解释器监控比率默认33,如果都取默认值,Server模式下回边计数器阈值应该是10700。

    当解释器遇到一条回边指令时,会先查找将要执行的代码片段中是否有已经编译好的版本,如果有,它将会优先执行已编译好的代码,否则就把回边计时器的值加1,然后判断方法调用计数器与回边计数器值之和是否已经超过回边计数器的阈值。当超过阈值之后,将会提交一个OSR编译请求,并且把回边计数器的值降低一些,以便继续在解释器中执行循环,等待编译器输出编译结果。

    与方法计数器不同,回边计数器没有热度衰减的过程,因此这个计数器统计的就是该方法循环执行的绝对次数。当计数器溢出的时候,它还会把方法计数器的值也调整到溢出状态,这样下次再进入该方法的时候就会执行标准编译过程。

    比如下面这段代码:

    // 参数:-XX:+PrintCompilation -XX:-TieredCompilation(关闭分层编译)
    private static Random random = new Random();
    
    public static void main(String[] args) {
      nlp();
    }
    
    public static void nlp() {
      long sum = 0;
      for (int i = 0; i < 18000; i++) {
        sum += random.nextInt(10);
      }
    }
    

    上述代码的编译结果仍然有 nlp 方法,可见触发了即时编译。顺便来看一下 nlp 方法的字节码内容。

     public static void nlp();
        descriptor: ()V
        flags: (0x0009) ACC_PUBLIC, ACC_STATIC
        Code:
          stack=4, locals=3, args_size=0
             0: lconst_0
             1: lstore_0
             2: iconst_0
             3: istore_2
             4: iload_2
             5: sipush        18000
             8: if_icmpge     29
            11: lload_0
            12: getstatic     #3                  // Field random:Ljava/util/Random;
            15: bipush        10
            17: invokevirtual #4                  // Method java/util/Random.nextInt:(I)I
            20: i2l
            21: ladd
            22: lstore_0
            23: iinc          2, 1
            26: goto          4
            29: return
    

    可以看到,偏移量为26的字节码将往回跳至偏移量为4的字节码中,则偏移量为 26的指令即为回边指令。解释器遇到该指令时,会先查找将要执行的代码片段中是否有已经编译好的版本,如果没有,则回边计数器加1,直到满足回边计数器的阈值。

    小结

    针对上述两种计数器,我们得到如下结论:

    • 每个方法都有两种计数器:方法调用计数器和回边计数器,每个计数器的阈值不同。
    • 方法调用计数器阈值 1500;回边计数器阈值 13995,当计数超过阈值时,则会触发C1编译。
    • 方法调用计数器阈值 10000;回边计数器阈值 10700,达到阈值则触发 C2 编译。

    另外需要注意的时,与阈值比较的都是方法调用计数器和回边计数器值之和,因为多次被调用的某个方法里可能有一个循环体。比如下面这段代码:

    //-XX:+PrintCompilation
    private static Random random = new Random();
    
    public static void main(String[] args) {
      long sum = 0;
      long start = System.nanoTime();
      for (int i = 0; i < 6000; i++) {
        sum += getRandomNum();
      }
      long end = System.nanoTime();
      System.out.println(end - start);
    }
    
    public static int getRandomNum() {
      return random.nextInt(10);
    }
    

    根据输出结果可知触发了 C1和 C2 编译,虽然循环次数只有 6000 次,但是却能触发 C2 编译,因为两种计数器都在计数,最后累加的和超过阈值即可触发即时编译。

    明显可以看出来方法调用计数器的阈值小一些,那么一般情况下只要满足了方法调用计数器的阈值就可以触发编译,为什么还要设置回边计数器的阈值,原因就在于 OSR。OSR 的触发条件更高一些罢了。

    OSR

    可以看到,决定一个方法是否为热点代码的因素有两个:方法的调用次数、回边的执行次数。即时编译便是根据这两个计数器的和来触发的。为什么 Java 虚拟机需要维护两个不同的计数器呢?

    对于这两种情况,编译的目标对象都是整个方法体,而不会是单独的循环体。第一种情况,由于是依靠方法调用触发的编译,那编译器理所当然地会以整个方法作为编译对象,这种编译也是虚拟机中标准的即时编译方式。

    而对于后一种情况, 尽管编译动作是由循环体所触发的,热点只是方法的一部分,但编译器依然必须以整个方法作为编译对象,只是执行入口(从方法第几条字节码指令开始执行) 会稍有不同,编译时会传入执行入口点字节码序号(Byte Code Index, BCI)(这点在上文回边计数器一节有解读过相应的字节码文件)。这种编译方式因为编译发生在方法执行的过程中,因此被很形象地称为“栈上替换”(On Stack Replacement, OSR),即方法的栈帧还在栈上, 方法就被替换了。

    在不启用分层编译的情况下,触发 OSR 编译的阈值是由参数 -XX:CompileThreshold 指定的阈值的倍数。默认情况下,C1 的 OSR 编译的阈值为 13500,而 C2 的为 10700。

    在启用分层编译的情况下,触发 OSR 编译的阈值则是由参数 -XX:TierXBackEdgeThreshold 指定的阈值乘以系数。

    Codecache

    通过上文的学习,我们也都知道 JVM 在运行时会将热点代码编译为本地机器码,以提高执行效率。那么这些机器码存在什么位置呢?接下来将引入一个术语——Codecache,用来存放本地机器码的内存区域了,它存在于堆外内存。一般情况下我们都接触不到这块区域,可能偶尔遇到线上服务器宕机了,在日志里面看到 java.lang.OutOfMemoryError code cache,就是这块内存发生的问题。

    配置

    Codecache 内存大小配置:

    InitialCodeCacheSize = 2555904	//默认大小
    ReservedCodeCacheSize = 251658240		//内存最大值
    CodeCacheExpansionSize =65536		//CodeCache每次扩展大小
    

    CodeCache输出参数的相关参数:

    -XX:+PrintCodeCache  # 在JVM停止的时候打印出codeCache的使用情况,其中max_used就是在整个运行过程中codeCache的最大使用量
    -XX:+PrintCodeCacheOnCompilation  # 用于在方法每次被编译时输出CodeCache的使用情况
    

    查看 CodeCache 使用情况,执行如下命令:

    -XX:+PrintCodeCache
    //结果如下:
    CodeCache: size=245760Kb used=1123Kb max_used=1132Kb free=244636Kb
     bounds [0x0000000118979000, 0x0000000118be9000, 0x0000000127979000]
     total_blobs=285 nmethods=33 adapters=166
     compilation: enabled
    

    如果想要修改 Codecache 内存最大值,可以这样设置:

    -XX:ReservedCodeCacheSize=2496K -XX:+PrintCodeCache
    //输出结果:
    CodeCache: size=2496Kb used=1109Kb max_used=1120Kb free=1386Kb
     bounds [0x000000010ae06000, 0x000000010b076000, 0x000000010b076000]
     total_blobs=274 nmethods=28 adapters=160
     compilation: enabled
    

    Codecache 刷新选项

    • UseCodeCacheFlushing,默认为 true,是否在关闭JIT编译前清除 CodeCache。

    CodeCache编译限制相关参数:

    -XX:MaxInlineLevel  # 针对嵌套调用的最大内联深度,默认为9
    -XX:MaxInlineSize  # 方法可以被内联的最大bytecode大小,默认为35
    -XX:MinInliningThreshold  # 方法可以被内联的最小调用次数,默认为250
    -XX:+InlineSynchronizedMethods  # 是否允许内联synchronized methods,默认为true
    

    Codecache 满了会怎么样

    Codecache 占用的内存有固定大小,JVM 将不会编译任何额外的代码,因为 JIT 编译器现在已关闭。此外,我们会收到*“CodeCache is full… The compiler has been disabled* ”的警告信息。JIT 编译器被停止了,并且不会被重新启动,此时会回归到解释执行,被编译过的代码仍然以编译方式执行,但是尚未被编译的代码就只能以解释方式执行了。

    增加 ReservedCodeCacheSize 可能是一种解决方案,但这通常只是一种临时解决方法。

    如果程序无法走编译执行,那么就会影响到程序的执行效率。JVM 提供了一个 UseCodeCacheFlushing 选项来控制代码缓存区域的刷新,该项配置默认为 true,会在满足以下条件时释放占用的区域:

    • code cache用满; 如果该区域的大小超过某个阈值,则会清除 Codecache。
    • 自上次清理后经过了一定的时间间隔。
    • 预编译的代码不够热。 对于每个JIT编译的方法,JVM都会有一个热度跟踪计数器。 如果计数器的值小于动态阈值,则JVM会释放这段预编译的代码。

    知道了何时会清除 Codecache,那么 JVM 接下来如何做?

    -XX:+PrintCompilation 命令用来输出程序编译信息,其中输出内容中包括这样两个标识:made non entrant 和 made zombie。

    made not entrant 表示被编译的方法不能再被进入,即无法再复用 Codecache 中的机器码,同时 JVM 会借助去优化(Deoptimization)机制,从执行即时编译器生成的机器码切换回解释执行,关于去优化,后续章节会详细介绍。

    当满足 Codecache 的清除条件时, Java 虚拟机检测到所有的线程都退出该编译后的“made not entrant”时,会将该方法标记为“made zombie”,此时可以回收这块代码所占据的空间了。

    扩展

    PrintCompilation 参数

    -XX:+PrintCompilation
    

    PrintCompilation 将会输出被编译方法的统计信息,因此使用 PrintCompilation 可以很方便的看出哪些是热点代码。热点代码也就意味着存在着被优化的可能性。默认情况下,禁用此选项并且不打印诊断输出。

    //添加参数-XX:+PrintCompilation
    public static void main(String[] args) {
      long start = System.nanoTime();
      for (int i = 0; i < 200; i++) {
        nlp();
      }
      long end = System.nanoTime();
      System.out.println(end - start);
    }
    
    public static void nlp() {
      int sum = 0;
      for (int i = 0; i < 2000; i++) {
        sum += i;
      }
    }
    

    截取部分日志输出为:

        185   24       4       sun.nio.cs.UTF_8$Encoder::encode (359 bytes)
        185   26       3       java.lang.StringBuilder::append (8 bytes)
        185   27       1       java.net.URL::getAuthority (5 bytes)
        186   28       3       java.lang.String::isEmpty (14 bytes)
        187   29 %     3       HotCodeTest::nlp @ 4 (22 bytes)
        187   30       3       HotCodeTest::nlp (22 bytes)
        187   31 %     4       HotCodeTest::nlp @ 4 (22 bytes)
        187   33       3       java.lang.System::getSecurityManager (4 bytes)
        187   32       3       java.lang.StringBuilder::toString (17 bytes)
        187   29 %     3       HotCodeTest::nlp @ -2 (22 bytes)   made not entrant
        187   34       4       HotCodeTest::nlp (22 bytes)
        188   35       3       java.util.Arrays::copyOfRange (63 bytes)
        188   30       3       HotCodeTest::nlp (22 bytes)   made not entrant
        188   36       4       java.lang.String::hashCode (55 bytes)
    

    简要介绍一下日志含义:

    • 第一列:方法开始编译时的时间(毫秒)

    • 第二列:Java 虚拟机维护的编译 ID

    • 第三列:一些 flag 标识

      b    Blocking compiler (always set for client)	//阻塞应用线程
      *    Generating a native wrapper
      %    On stack replacement (where the compiled code is running)	//由回边计数器触发的栈上替换编译
      !    Method has exception handlers	//包含异常处理器的方法
      s    Method declared as synchronized	//同步方法
      n    Method declared as native //本地方法
      made non entrant    compilation was wrong/incomplete, no future callers will use this version	//表示方法不能再被进入
      made zombie         code is not in use and ready for GC	//表示这块代码可以被回收空间了
      
    • 第四列:编译的层次,0-4层,对应分层编译章节中的那张图片

    • 第五列:编译的方法名

    CITime参数

    -XX:+CITime
    

    打印消耗在JIT编译的时间

    参考文献

    Introduction to Graal

    深入剖析Java即时编译器(上)

    基本功 | Java即时编译器原理解析及实践

    JVM代码缓存介绍

    更多相关内容
  • 即时编译器 (JIT) 详解

    千次阅读 2021-02-12 22:11:02
    最近听我的导师他们讨论Java的即时编译器(JIT),当时并不知道这是啥东西,所以就借着周末的时间,学习了一下!一、概述在部分的商用虚拟机(Sun HotSpot)中,Java程序最初是通过解释器(Interpreter)进行解释执行的,...

    最近听我的导师他们讨论Java的即时编译器(JIT),当时并不知道这是啥东西,所以就借着周末的时间,学习了一下!

    一、概述

    在部分的商用虚拟机(Sun HotSpot)中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块运行的特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code)。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些热点代码编译成与本地代码相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文中简称JIT编译器)。

    即时编译器并不是虚拟机必须的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器存在,更没有规定或指导即时编译器应该如何去实现。但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它是虚拟机中最核心且最能体现虚拟机技术水平的部分。

    二、HotSpot 虚拟机内的即时编译器

    2.1 解释器与编译器

    尽管并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机,如HtoSpot,J9等,都同时包含解释器与编译器。解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编程成本地代码之后,获取更搞得执行效率。当程序运行环境中内存资源限制较大,可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。同时,解释器还可以作为编译器进行优化时的一个“逃生门”,让编译器根据概率选择一些大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立,如加载了新类后类型继承结构出现变化、出现了“罕见陷阱”时可以通过逆优化退回到解释状态继续执行(部分没有解释器的虚拟机中也会采用不进行激进优化的C1编译器--担任“逃生门”的角色),因此,在整个虚拟机执行架构中,解释器与编译器经常配合工作,如图所示:

    10d5d4c5b0e49fbfa5fbbc983364a97b.png

    HotSpot虚拟机中内置有两个即时编译器,分别称为Client Compiler和Server Compiler,或者简称为C1编译器和C2编译器。目前主流的HotSpot虚拟机中,默认采用解释器与其中一个编译器直接配合的方式工作,程序使用哪个编译器,取决于虚拟机运行模式,HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数指定虚拟机运行在Client模式或Server模式!

    虚拟机默认采用“混合模式”进行Java代码编译后执行,可以使用 -Xint优先采用解释器解释执行;使用-Xcomp 优先采用即时编译器编译执行,但是解释器仍然要在编译无法进行的情况下介入执行过程!

    45104ec2979e91da99137eeaac3b3260.png

    由于即时编译器编译本地代码需要占用程序运行时间,要编译出优化程度更高的代码,所花费的时间可能更长;而且想要编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息,这对解释执行的速度也有影响。为了在程序启动响应速度与运行效率之间达到最佳平衡,HotSpot虚拟机还会逐渐启动分层编译的策略,分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次,其中包括:

    第0层,程序解释执行,解释器不开启性能监控功能(Profiling),可触发第1层编译。

    第1层,也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑;

    第2层,也称为C2编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

    实施分层编译后,Client Compiler和Server Compiler将同时工作,许多代码都可能会被多次编译,用Client Compiler获取更高的编译速度,用Server Compiler获取更好的编译质量,在解释执行的时候也无需再承担收集性能监控信息的任务!

    2.2 编译对象和触发条件

    上文中提到过,在运行过程中会被即时编译器编译的“热点代码”有两类:

    1)被多次调用的方法;

    2)被多次执行的循环体;

    对于第一种情况,由于是由方法调用触发的编译,因此编译器理所当然地会以整个方法作为编译对象,这种编译也是虚拟机中标准得JIT编译方法。而对于后一种情况,尽管编译动作由循环体所触发的,但编译器依然会以整个方法(而不是单独的循环体)作为编译对象。这种编译方式因为编译发生在方法执行的过程中,因此形象的称之为栈上替换(On Stack Replacement,简称OSR编译,即方法栈帧还在栈上,方法就被替换了)。

    基于采样的热点探测(Sample Based Hot Spot Detection):采用这种方法的虚拟机会周期性地检测各个线程的栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。基于采样的热点探测的好处是实现简单、高效、还可以很容易地获取方法调用关系,缺点就是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测;

    基于计数器的热点探测(Counter Based Hot Spot Detection):采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计代码的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。这种统计方法实现起来麻烦一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对来说更加精确和严格!

    在HotSpot虚拟机中使用的第二种---基于计数器的热点探测方法,因此它为每个方法准备了两类计数器:方法调用计数器(Invocation counter)和回边计数器(Back Edge counter)。当计数器超过阈值溢出了,就会触发JIT编译。

    23a2ea028ea35e41fa222c8d26601c07.png

    77a649753ff11ee8b6dd9e6636ef57ff.png

    回边计数器:它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为”回边“。目的就是为了触发OSR编译。

    默认在Client模式下,回边计数器的阈值是13995;在Server模式下的阈值是10700。

    4b4fe6ae0c0a20b91df4d1f5b5ef8ca4.png

    ba930c8c688530a2f41302302b915984.png

    2.3 编译过程

    a0116e54940f28f3fabddf521d9198fd.png

    而Server Compiler则是专门面向服务端的典型应用并为服务端的性能配置特别调整过的编译器,它执行所有经典的优化动作,如无用代码消除、循环展开、常量传播等。以即时编译器的标准来看,Server Compiler无疑是比较缓慢的,但它的编译速度依然远远超过传统的静态优化编译器,而且它相对于Client Compiler编译输出的代码质量有所提高,可以减少本地代码的执行时间,从而抵消了额外的编译时间开销,所以也有很多非服务端的应用选择使用Server模式的虚拟机运行。

    三、编译优化技术

    Java程序员有一个共识,以编译方式执行本地代码比解释方式更快,之所以有这样的共识,除去虚拟机解释执行字节码时额外消耗时间的原因外,还有一个很重要的原因就是虚拟机设计团队几乎把代码的所有优化措施都集中在了即时编译器之中,因此一般来说,即时编译器生成的本地代码比Javac产生的字节码更加优秀!

    3.1 方法内联优化

    方法内联具有很高的重要性,一是去除方法调用的成本(如建立栈帧等),而是为了其他优化建立良好的基础!

    注意:方法的调用过程发生的时期?

    (1) 首先会有个执行栈,存储目前所有活跃的方法,以及它们的本地变量和参数;

    (2) 当一个新的方法被调用了,一个新的栈帧会被加到当前线程的栈顶,分配的本地变量和参数会存储在这个栈帧中;

    (3) 跳到目标方法代码执行;

    (4) 方法返回的时候,本地方法和参数会被销毁,栈顶被移除;

    (5) 返回原来地址执行;

    3.1.1 方法内联的原理

    方法内联就是把被调用方函数代码”复制”到调用方函数中,来减少因函数调用开销的技术。

    我们写一个简单的两数相加程序,被内联前的代码:

    private int add1(int a, int b, int c, int d){

    return add2(a + b) + add2(c + d);

    }

    private int add2(int x, int y){

    return x + y;

    }

    运行一段时间后JVM会把add2方法去掉,并把你的代码翻译成:

    private int add1(int a, int b, int c, int d){

    return a + b + c + d;

    }

    1)方法内联的其他隐含条件:

    虽然JIT号称可以针对代码全局的运行情况而优化,但是JIT对一个方法内联之后,还是可能因为方法被继承,导致需要类型检查而没有达到性能的效果

    想要对热点的方法使用上内联的优化方法,最好尽量使用final、private、static这些修饰符修饰方法,避免方法因为继承,导致需要额外的类型检查,而出现效果不好情况。

    2)如果想要知道方法被内联的情况,可以使用下面的JVM参数来配置:

    -XX:+PrintCompilation //在控制台打印编译过程信息

    -XX:+UnlockDiagnosticVMOptions //解锁对JVM进行诊断的选项参数。默认是关闭的,开启后支持一些特定参数对JVM进行诊断

    -XX:+PrintInlining //将内联方法打印出来

    3)编译器在进行内联时,如果是非虚方法,那么直接进行内联就可以了,这时候的内联是有稳定前提保障的。(虚方法即为子类继承/实现父类,重写的方法)

    684602226cff454d270f5144352a4998.png

    3.2 优化技术

    1)优化前的原始代码:

    public class B{

    int value;

    final int get(){

    return value;

    }

    public void foo(){

    y = b.get();

    //......do stuff......

    y = b.get();

    sum = y + z;

    }

    }

    2)内联后的代码:

    public void foo(){

    y = b.value

    //......do stuff......

    z = b.value

    sum = y + z;

    }

    3)冗余diaman存储消除的代码:

    public void foo(){

    y = b.value

    //......do stuff......

    z = y

    sum = y + z;

    }

    把”z=b.value”替换为”z=y“,因为上一句”y=b.value“已经保证了变量y和b.value是一致的,这样就可以不再去访问对象b的局部变量了!

    4)复写传播代码:

    public void foo(){

    y = b.value

    //......do stuff......

    y = y

    sum = y + y;

    }

    5)进行无用代码消除:

    public void foo(){

    y = b.value

    //......do stuff......

    sum = y + y;

    }

    3.3 公共子表达式消除

    如果一个表达式E已经计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就成为了公共子表达式!

    例如:int d = (c + b) * 12 + a + (a + b * c);

    1)代码交给Javac编译器则不会进行任何优化:(Javac编译器编译后的字节码展示)

    07ad57f8d94fcf437ab22b31881e5d35.png

    2)代码进入即时编译器JIT中:

    int d = E * 12 + a + (a + E);

    3) 这时即时编译器有可能进行另外一种优化:代数简化

    int d = E * 13 + a * 2;

    3.4 数组边界检查消除

    Java语言是一门动态安全的语言。如果有一个数组foo[],在Java语言中访问数组元素foo[i]的时候系统将会自动进行上下界的范围检查,即检查i必须满足i >=0 && i < foo.length这个条件,否则将抛出一个运行时异常:java.lang.ArrayIndexOutOfBoundsException。

    无论如何,为了安全,数组便捷检查肯定是必须做的,但数组边界检查是不是必须在运行期间一次不漏的检查则是可以“商量”的事情。例如:数组下标是一个常量,如 foo[3],只要在编译期根据数据流分析来确定foo.length的值,并判断下标“3”没有越界,执行的时候就无需判断了。

    3.5 逃逸分析

    逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定以后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有其可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸!

    四、Java编译器和C/C++的编译器相对

    01c3f6de4d45122165ffdf00b3d3cc8a.png

    参考资料:《深入了解Java虚拟机》

    展开全文
  • JIT即时编译器

    2022-05-19 15:44:12
    文章目录一、解释器与编译器二、热点代码与热点探测2.1、方法调用计数器2.2、回边计数器三、分层编译四、编译优化4.1、方法内联4.2、标量替换 一、解释器与编译器 Java程序在运行的时候,主要就是执行字节码指令,...

    一、解释器与编译器

    Java程序在运行的时候,主要就是执行字节码指令,一般这些指令会通过解释器(Interpreter)进行解释执行,这种就是解释执行。

    当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为 热点代码。为了提高热点代码的执行效率,在运行时虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,简称 JIT 编译器)。
    在这里插入图片描述
    在HotSpot虚拟机中内置了两个即时编译器,分别称为Client Compiler和Server Compiler,或者简称 C1编译器 和 C2编译器。

    • C1编译器
      一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序。开启的时间较早,应用启动后不久C1就开始进行编译

    • C2编译器
      是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。运行时间较晚,会等程序运行一段时候后才开始进行编译


    无论采用的编译器是 C1 还是 C2,解释器与编译搭配使用的方式在虚拟机中称为“混合模式”。
    在这里插入图片描述
    可以通过参数 -Xint 强制虚拟机运行于“解释器模式”,这里编译器完全不介于工作,全部代码都使用解释方式执行。

    还可以使用参数 -Xcomp 强制虚拟机运行于“编译器模式”,这时将优先采用编译方式执行程序,但是解释器仍然要在编译无法进行的情况下介入执行过程。


    二、热点代码与热点探测

    热点代码,就是那些被频繁调用的代码,这些会被编译进行被缓存,以备下次使用,但对于那些执行次数很少的代码来说,这种编译动作就纯属浪费。


    JVM 提供了一个参数 -XX:ReservedCodeCacheSize ,用来限制 CodeCache 的大小,JIT编译后的代码都会放在 CodeCache 里。 JDK7默认值为 32m~48m,JDK8默认值为240m。

    如果该空间不足,JIT就无法继续编译,编译执行会变成解释执行,性能就会较低。同时JIT编译器还会一直尝试去优化代码,从而造成了CPU占用上升。


    判断一段代码是不是热点代码,是不是需要出发即时编译,这样的行为称为热点探测,主要的热点探测判定方式有两种,分别如下:

    1. 基于采样的热点探测: 采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。

      缺点:基于采样的热点探测的好处就是实现简单、高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易受到线程堵塞或别的外界因素的影响而扰乱热点探测。

    2. 基于计数器的热点探测: 采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。

      缺点: 这种统计方法实现起来麻烦一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对来说更加精确和严谨。


    HotSpot虚拟机就是基于第二种——基于计数器的热点探测,它为每个方法准备了两类计数器:方法调用计数器和回边计数器

    2.1、方法调用计数器

    用于统计方法被调用的次数,它默认的阈值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次,这个阈值可以通过虚拟机参数 -XX:CompileThreshold 来人为设定。

    当一个方法被调用时,会先检查该方法是否存在JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行,如果不存在已被编译过的版本,则将此方法的调用计数器加1,然后判断方法调用计数器与回边调用计数器之和是否超过方法调用计数器的阈值,如果已超过阈值,那么就会向即时编译器提交一个该方法的代码编译请求。


    如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,及一段时间之内方法被调用的次数。当超过一定的时间限制,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程被称为方法调用计数器热度的衰减。

    对于上述所说的方法调用计数器热度的衰减,我们也是可以通过虚拟机参数 -XX:-UseCounterDecay 进行关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样只要系统运行时间足够长,绝大部分代码就会被编译器编译出本地代码。另外,还可以使用 -XX:CounterHalfLifeTime 参数来设置半衰周期的时间,单位是秒。


    2.2、回边计数器

    它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳的指令称为“回边”。与方法调用计数器不同的是,回边计数器没有计数热度衰减的过程,因此这个计数器统计的就是该方法循环执行的绝对次数。


    三、分层编译

    基于上述所说的 C1 和 C2 两种编译器的优缺点,虚拟机一般会启动分层编译的策略(开启分层编译参数:-XX:+TieredCompilation),分层编译根据编译器编译、优化的规模与耗时,划分出不同的编译层次,其中包括:

    1. 第0层,程序解释执行,解释器开始性能监控功能,可触发第1层编译。
    2. 第1层,也称为C1编译,将字节码编译出本地代码,进行简单、可靠的优化,如果必要将加入性能监控的逻辑。
    3. 第2层,也称为C2编译,也是将字节码编译为本地代码,但是会启动一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

    实施分层编译后,C1 和 C2 将会同时工作,许多代码都可能会被多次编译,用C1编译器获取更高的编译速度,用C2编译器来获取更多的编译质量。


    四、编译优化

    4.1、方法内联

    方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。
    在这里插入图片描述
    JVM会自动识别热点方法,并对它们使用方法内联进行优化。但热点方法不一定会被JVM做内联优化,比如该方法体太大,JVM将不执行内联操作,默认情况下方法体小于 325 字节的都会进行内联,我们可以通过 -XX:FreqInlineSize=N 来设置大小值。


    4.2、标量替换

    逃逸分析证明一个对象不会被外部访问,如果这个对象可以被拆分的话,当程序真正执行的时候可能不创建这个对象,而直接创建它的成员变量来代替。
    在这里插入图片描述
    将对象拆分后,可以分配对象的成员变量在栈或寄存器上,原本的对象就无需分配内存空间了。这种编译优化就叫做标量替换(前提是需要开启逃逸分析)。

    展开全文
  • JVM的执行引擎执行字节码通过两种解释器执行的:字节码解释器与模板解释器,运行过程中,可能会触发即时编译(JIT),涉及到几种即时编译器,下面分别进行介绍。 字节码解释器 字节码解释器是解释执行的,所谓...

    什么是JVM执行引擎?

    执行引擎是Java虚拟机四大组成部分中另一个核心组成(另外三个分别是类加载器子系统、运行时数据区、垃圾回收器),Java虚拟机的执行引擎主要是用来执行Java字节码。JVM的执行引擎执行字节码通过两种解释器执行的:字节码解释器与模板解释器,运行过程中,可能会触发即时编译(JIT),涉及到几种即时编译器,下面分别进行介绍。

     

    字节码解释器

    字节码解释器是解释执行的,所谓解释执行,就是将Java字节码转成C++代码,再将C++代码编译成本地代码(硬编码),之所以会转成C++代码,是因为HotSpot虚拟机(本文中所说的Java虚拟机都是指HotSpot虚拟机)是C++代码编写的,所以Java字节码指令的底层实现都是由C++代码实现,执行字节码指令其实就是执行对应的C++代码,而执行C++代码之前会将C++代码编译成本地代码,然后再执行。

    所以,字节码解释器在工作的时候,它是逐行代码进行解释执行,准确地说是逐条字节码指令解释执行,由于字节码指令操作对象是栈帧中的操作数栈(伴随着入栈、出栈操作),因此我们常说字节码解释器是“基于栈的字节码解释执行引擎”。

    为了更深入地理字节码解释器的执行过程,下面通过一段简单的程序来进行说明:

    public int add(){
        int a = 10;
        int b = 20;
        return a + b;
    }

    通过javap命令(或者idea中的jclasslib插件),生成字节码如下:

    public int add();
        descriptor: ()I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=1
             0: bipush        10
             2: istore_1
             3: bipush        20
             5: istore_2
             6: iload_1
             7: iload_2
             8: iadd
             9: ireturn

    由上面这段字节码可知,Java虚拟机会为这个add()方法分配深度为2的操作数栈和个数为3的局部变量表(Code属性中包含了操作数栈深度和局部变量表大小,详细可以了解一下class文件结构)。

     

    为什么局部变量表的大小是3个呢,程序中明明只有a、b两个局部变量啊?是因为add()方法是非对象实例方法(<init>方法)的局部变量表中索引0的位置永远是this指针。我们来使用画图的方式来演示一下上面字节码指令的执行过程。

    1) 0:bipush 10

    这条指令的作用是将单字节的常量值 (-128~127) 推送至栈顶,这里10是指令的参数,就是将10压入操作数栈的栈 顶。

    2) istore_1

    这条指令的作用是将栈顶 int 型数值出栈,并存入局部变量表中索引为1的位置

     

    3)bipush 20

    同1),将20压入操作数栈的栈顶

     

    4)istore_2

    将操作数栈顶元素出栈,并存入局部变量表索引为2的位置

     

    5) iload_1、iload_2

    接下来这两条指令是分别将局部变量表中索引为1和索引为2的元素复制到操作数栈的栈顶,执行完结果如下:

     

    6)iadd

    将操作数栈中头两个栈顶元素分别出栈,然后进行加法运算,并将运算结果压入栈顶

    7)ireturn

    结束方法运行,并将栈顶整型元素返回给方法调用者。

     

    经过这段代码执行过程的分析可以看出,字节码解释器的执行过程就是逐条执行字节码指令。

    需要注意的是,上面所说的逐条字节码指令进行解释执行,是指Java虚拟机规范中给出的一种模型,而实际上Java虚拟机并不会按部就班地根据字节码指令逐条执行,因为虚拟机会对字节码指令做一系列优化(如指令重排),而且现在的虚拟机都是解释执行和编译执行混合模式,执行时的优化程度会更大,所以这里所说的只是一种概念模型

    通过对字节码解释器的了解可知,它是逐条指令来进行转成C++代码,然后再编译成本地代码,最后执行,所以它执行速度比较慢,早期的JVM就是解释执行的,当然,这种方式已经无法适用现在应用的发展,所以就有了了模板解释器。

     

    模板解释器

    模板解释器是编译执行的,它的执行过程是直接将Java字节码编译成本地代码(硬编码),然后执行本地代码。相当于对于每一条字节码指令,都将它最后生成的本地代码给提前写死了,JVM初始化的时候,就会直接将字节码指令对应的本地代码给加载到内存中,执行的时候直接执行对应的本地代码即可,这样就跳过了字节码转C++以及由C++编译成本地代码的过程,提高了执行速度。如下代码所示,JVM初始化时会将每一个字节码指令的模板都创建处理,def函数就是为字节码指令创建模板的,每个模板都会生成该指令对应的本地代码。

    void TemplateTable::initialize() {
      if (_is_initialized) return;
    
      // Initialize table
      TraceTime timer("TemplateTable initialization", TraceStartupTime);
    
      _bs = Universe::heap()->barrier_set();
    
      // For better readability
      const char _    = ' ';
      const int  ____ = 0;
      const int  ubcp = 1 << Template::uses_bcp_bit;
      const int  disp = 1 << Template::does_dispatch_bit;
      const int  clvm = 1 << Template::calls_vm_bit;
      const int  iswd = 1 << Template::wide_bit;
      //                                    interpr. templates
      // Java spec bytecodes                ubcp|disp|clvm|iswd  in    out   generator             argument
      def(Bytecodes::_nop                 , ____|____|____|____, vtos, vtos, nop                 ,  _           );
      def(Bytecodes::_aconst_null         , ____|____|____|____, vtos, atos, aconst_null         ,  _           );
      def(Bytecodes::_iconst_m1           , ____|____|____|____, vtos, itos, iconst              , -1           );
      def(Bytecodes::_iconst_0            , ____|____|____|____, vtos, itos, iconst              ,  0           );
      def(Bytecodes::_iconst_1            , ____|____|____|____, vtos, itos, iconst              ,  1           );
      def(Bytecodes::_iconst_2            , ____|____|____|____, vtos, itos, iconst              ,  2           );
      def(Bytecodes::_iconst_3            , ____|____|____|____, vtos, itos, iconst              ,  3           );
      def(Bytecodes::_iconst_4            , ____|____|____|____, vtos, itos, iconst              ,  4           );
      def(Bytecodes::_iconst_5            , ____|____|____|____, vtos, itos, iconst              ,  5           );
      def(Bytecodes::_lconst_0            , ____|____|____|____, vtos, ltos, lconst              ,  0           );
      def(Bytecodes::_lconst_1            , ____|____|____|____, vtos, ltos, lconst              ,  1           );
      def(Bytecodes::_fconst_0            , ____|____|____|____, vtos, ftos, fconst              ,  0           );
      def(Bytecodes::_fconst_1            , ____|____|____|____, vtos, ftos, fconst              ,  1           );
      def(Bytecodes::_fconst_2            , ____|____|____|____, vtos, ftos, fconst              ,  2           );
      def(Bytecodes::_dconst_0            , ____|____|____|____, vtos, dtos, dconst              ,  0           );
      def(Bytecodes::_dconst_1            , ____|____|____|____, vtos, dtos, dconst              ,  1           );
      def(Bytecodes::_bipush              , ubcp|____|____|____, vtos, itos, bipush              ,  _           );
      def(Bytecodes::_sipush              , ubcp|____|____|____, vtos, itos, sipush              ,  _           );
      def(Bytecodes::_ldc                 , ubcp|____|clvm|____, vtos, vtos, ldc                 ,  false       );
      def(Bytecodes::_ldc_w               , ubcp|____|clvm|____, vtos, vtos, ldc                 ,  true        );
      def(Bytecodes::_ldc2_w              , ubcp|____|____|____, vtos, vtos, ldc2_w              ,  _           );
      def(Bytecodes::_iload               , ubcp|____|clvm|____, vtos, itos, iload               ,  _           );
      def(Bytecodes::_lload               , ubcp|____|____|____, vtos, ltos, lload               ,  _           );
      def(Bytecodes::_fload               , ubcp|____|____|____, vtos, ftos, fload               ,  _           );
      def(Bytecodes::_dload               , ubcp|____|____|____, vtos, dtos, dload               ,  _           );
      def(Bytecodes::_aload               , ubcp|____|clvm|____, vtos, atos, aload               ,  _           );
      def(Bytecodes::_iload_0             , ____|____|____|____, vtos, itos, iload               ,  0           );
      def(Bytecodes::_iload_1             , ____|____|____|____, vtos, itos, iload               ,  1           );
      def(Bytecodes::_iload_2             , ____|____|____|____, vtos, itos, iload               ,  2           );
      def(Bytecodes::_iload_3             , ____|____|____|____, vtos, itos, iload               ,  3           );
      def(Bytecodes::_lload_0             , ____|____|____|____, vtos, ltos, lload               ,  0           );
      def(Bytecodes::_lload_1             , ____|____|____|____, vtos, ltos, lload               ,  1           );
      def(Bytecodes::_lload_2             , ____|____|____|____, vtos, ltos, lload               ,  2           );
      def(Bytecodes::_lload_3             , ____|____|____|____, vtos, ltos, lload               ,  3           );
      def(Bytecodes::_fload_0             , ____|____|____|____, vtos, ftos, fload               ,  0           );
      def(Bytecodes::_fload_1             , ____|____|____|____, vtos, ftos, fload               ,  1           );
      def(Bytecodes::_fload_2             , ____|____|____|____, vtos, ftos, fload               ,  2           );
      def(Bytecodes::_fload_3             , ____|____|____|____, vtos, ftos, fload               ,  3           );
      def(Bytecodes::_dload_0             , ____|____|____|____, vtos, dtos, dload               ,  0           );
      def(Bytecodes::_dload_1             , ____|____|____|____, vtos, dtos, dload               ,  1           );
      def(Bytecodes::_dload_2             , ____|____|____|____, vtos, dtos, dload               ,  2           );
      def(Bytecodes::_dload_3             , ____|____|____|____, vtos, dtos, dload               ,  3           );
      def(Bytecodes::_aload_0             , ubcp|____|clvm|____, vtos, atos, aload_0             ,  _           );
      def(Bytecodes::_aload_1             , ____|____|____|____, vtos, atos, aload               ,  1           );
      def(Bytecodes::_aload_2             , ____|____|____|____, vtos, atos, aload               ,  2           );
      def(Bytecodes::_aload_3             , ____|____|____|____, vtos, atos, aload               ,  3           );
      def(Bytecodes::_iaload              , ____|____|____|____, itos, itos, iaload              ,  _           );
      def(Bytecodes::_laload              , ____|____|____|____, itos, ltos, laload              ,  _           );
      def(Bytecodes::_faload              , ____|____|____|____, itos, ftos, faload              ,  _           );
      def(Bytecodes::_daload              , ____|____|____|____, itos, dtos, daload              ,  _           );
      def(Bytecodes::_aaload              , ____|____|____|____, itos, atos, aaload              ,  _           );
      def(Bytecodes::_baload              , ____|____|____|____, itos, itos, baload              ,  _           );
      def(Bytecodes::_caload              , ____|____|____|____, itos, itos, caload              ,  _           );
      def(Bytecodes::_saload              , ____|____|____|____, itos, itos, saload              ,  _           );
      def(Bytecodes::_istore              , ubcp|____|clvm|____, itos, vtos, istore              ,  _           );
      def(Bytecodes::_lstore              , ubcp|____|____|____, ltos, vtos, lstore              ,  _           );
      def(Bytecodes::_fstore              , ubcp|____|____|____, ftos, vtos, fstore              ,  _           );
      def(Bytecodes::_dstore              , ubcp|____|____|____, dtos, vtos, dstore              ,  _           );
      def(Bytecodes::_astore              , ubcp|____|clvm|____, vtos, vtos, astore              ,  _           );
      def(Bytecodes::_istore_0            , ____|____|____|____, itos, vtos, istore              ,  0           );
      def(Bytecodes::_istore_1            , ____|____|____|____, itos, vtos, istore              ,  1           );
      def(Bytecodes::_istore_2            , ____|____|____|____, itos, vtos, istore              ,  2           );
      def(Bytecodes::_istore_3            , ____|____|____|____, itos, vtos, istore              ,  3           );
      def(Bytecodes::_lstore_0            , ____|____|____|____, ltos, vtos, lstore              ,  0           );
      def(Bytecodes::_lstore_1            , ____|____|____|____, ltos, vtos, lstore              ,  1           );
      def(Bytecodes::_lstore_2            , ____|____|____|____, ltos, vtos, lstore              ,  2           );
      def(Bytecodes::_lstore_3            , ____|____|____|____, ltos, vtos, lstore              ,  3           );
      def(Bytecodes::_fstore_0            , ____|____|____|____, ftos, vtos, fstore              ,  0           );
      def(Bytecodes::_fstore_1            , ____|____|____|____, ftos, vtos, fstore              ,  1           );
      def(Bytecodes::_fstore_2            , ____|____|____|____, ftos, vtos, fstore              ,  2           );
      def(Bytecodes::_fstore_3            , ____|____|____|____, ftos, vtos, fstore              ,  3           );
      def(Bytecodes::_dstore_0            , ____|____|____|____, dtos, vtos, dstore              ,  0           );
      def(Bytecodes::_dstore_1            , ____|____|____|____, dtos, vtos, dstore              ,  1           );
      def(Bytecodes::_dstore_2            , ____|____|____|____, dtos, vtos, dstore              ,  2           );
      def(Bytecodes::_dstore_3            , ____|____|____|____, dtos, vtos, dstore              ,  3           );
      def(Bytecodes::_astore_0            , ____|____|____|____, vtos, vtos, astore              ,  0           );
      def(Bytecodes::_astore_1            , ____|____|____|____, vtos, vtos, astore              ,  1           );
      def(Bytecodes::_astore_2            , ____|____|____|____, vtos, vtos, astore              ,  2           );
      def(Bytecodes::_astore_3            , ____|____|____|____, vtos, vtos, astore              ,  3           );
      def(Bytecodes::_iastore             , ____|____|____|____, itos, vtos, iastore             ,  _           );
      def(Bytecodes::_lastore             , ____|____|____|____, ltos, vtos, lastore             ,  _           );
      def(Bytecodes::_fastore             , ____|____|____|____, ftos, vtos, fastore             ,  _           );
      def(Bytecodes::_dastore             , ____|____|____|____, dtos, vtos, dastore             ,  _           );
      def(Bytecodes::_aastore             , ____|____|clvm|____, vtos, vtos, aastore             ,  _           );
      def(Bytecodes::_bastore             , ____|____|____|____, itos, vtos, bastore             ,  _           );
      def(Bytecodes::_castore             , ____|____|____|____, itos, vtos, castore             ,  _           );
      def(Bytecodes::_sastore             , ____|____|____|____, itos, vtos, sastore             ,  _           );
      def(Bytecodes::_pop                 , ____|____|____|____, vtos, vtos, pop                 ,  _           );
      def(Bytecodes::_pop2                , ____|____|____|____, vtos, vtos, pop2                ,  _           );
      def(Bytecodes::_dup                 , ____|____|____|____, vtos, vtos, dup                 ,  _           );
      def(Bytecodes::_dup_x1              , ____|____|____|____, vtos, vtos, dup_x1              ,  _           );
      def(Bytecodes::_dup_x2              , ____|____|____|____, vtos, vtos, dup_x2              ,  _           );
      def(Bytecodes::_dup2                , ____|____|____|____, vtos, vtos, dup2                ,  _           );
      def(Bytecodes::_dup2_x1             , ____|____|____|____, vtos, vtos, dup2_x1             ,  _           );
      def(Bytecodes::_dup2_x2             , ____|____|____|____, vtos, vtos, dup2_x2             ,  _           );
      def(Bytecodes::_swap                , ____|____|____|____, vtos, vtos, swap                ,  _           );
      def(Bytecodes::_iadd                , ____|____|____|____, itos, itos, iop2                , add          );
      def(Bytecodes::_ladd                , ____|____|____|____, ltos, ltos, lop2                , add          );
      def(Bytecodes::_fadd                , ____|____|____|____, ftos, ftos, fop2                , add          );
      def(Bytecodes::_dadd                , ____|____|____|____, dtos, dtos, dop2                , add          );
      def(Bytecodes::_isub                , ____|____|____|____, itos, itos, iop2                , sub          );
      def(Bytecodes::_lsub                , ____|____|____|____, ltos, ltos, lop2                , sub          );
      def(Bytecodes::_fsub                , ____|____|____|____, ftos, ftos, fop2                , sub          );
      def(Bytecodes::_dsub                , ____|____|____|____, dtos, dtos, dop2                , sub          );
      def(Bytecodes::_imul                , ____|____|____|____, itos, itos, iop2                , mul          );
      def(Bytecodes::_lmul                , ____|____|____|____, ltos, ltos, lmul                ,  _           );
      def(Bytecodes::_fmul                , ____|____|____|____, ftos, ftos, fop2                , mul          );
      def(Bytecodes::_dmul                , ____|____|____|____, dtos, dtos, dop2                , mul          );
      def(Bytecodes::_idiv                , ____|____|____|____, itos, itos, idiv                ,  _           );
      def(Bytecodes::_ldiv                , ____|____|____|____, ltos, ltos, ldiv                ,  _           );
      def(Bytecodes::_fdiv                , ____|____|____|____, ftos, ftos, fop2                , div          );
      def(Bytecodes::_ddiv                , ____|____|____|____, dtos, dtos, dop2                , div          );
      def(Bytecodes::_irem                , ____|____|____|____, itos, itos, irem                ,  _           );
      def(Bytecodes::_lrem                , ____|____|____|____, ltos, ltos, lrem                ,  _           );
      def(Bytecodes::_frem                , ____|____|____|____, ftos, ftos, fop2                , rem          );
      def(Bytecodes::_drem                , ____|____|____|____, dtos, dtos, dop2                , rem          );
      def(Bytecodes::_ineg                , ____|____|____|____, itos, itos, ineg                ,  _           );
      def(Bytecodes::_lneg                , ____|____|____|____, ltos, ltos, lneg                ,  _           );
      def(Bytecodes::_fneg                , ____|____|____|____, ftos, ftos, fneg                ,  _           );
      def(Bytecodes::_dneg                , ____|____|____|____, dtos, dtos, dneg                ,  _           );
      def(Bytecodes::_ishl                , ____|____|____|____, itos, itos, iop2                , shl          );
      def(Bytecodes::_lshl                , ____|____|____|____, itos, ltos, lshl                ,  _           );
      def(Bytecodes::_ishr                , ____|____|____|____, itos, itos, iop2                , shr          );
      def(Bytecodes::_lshr                , ____|____|____|____, itos, ltos, lshr                ,  _           );
      def(Bytecodes::_iushr               , ____|____|____|____, itos, itos, iop2                , ushr         );
      def(Bytecodes::_lushr               , ____|____|____|____, itos, ltos, lushr               ,  _           );
      def(Bytecodes::_iand                , ____|____|____|____, itos, itos, iop2                , _and         );
      def(Bytecodes::_land                , ____|____|____|____, ltos, ltos, lop2                , _and         );
      def(Bytecodes::_ior                 , ____|____|____|____, itos, itos, iop2                , _or          );
      def(Bytecodes::_lor                 , ____|____|____|____, ltos, ltos, lop2                , _or          );
      def(Bytecodes::_ixor                , ____|____|____|____, itos, itos, iop2                , _xor         );
      def(Bytecodes::_lxor                , ____|____|____|____, ltos, ltos, lop2                , _xor         );
      def(Bytecodes::_iinc                , ubcp|____|clvm|____, vtos, vtos, iinc                ,  _           );
      def(Bytecodes::_i2l                 , ____|____|____|____, itos, ltos, convert             ,  _           );
      def(Bytecodes::_i2f                 , ____|____|____|____, itos, ftos, convert             ,  _           );
      def(Bytecodes::_i2d                 , ____|____|____|____, itos, dtos, convert             ,  _           );
      def(Bytecodes::_l2i                 , ____|____|____|____, ltos, itos, convert             ,  _           );
      def(Bytecodes::_l2f                 , ____|____|____|____, ltos, ftos, convert             ,  _           );
      def(Bytecodes::_l2d                 , ____|____|____|____, ltos, dtos, convert             ,  _           );
      def(Bytecodes::_f2i                 , ____|____|____|____, ftos, itos, convert             ,  _           );
      def(Bytecodes::_f2l                 , ____|____|____|____, ftos, ltos, convert             ,  _           );
      def(Bytecodes::_f2d                 , ____|____|____|____, ftos, dtos, convert             ,  _           );
      def(Bytecodes::_d2i                 , ____|____|____|____, dtos, itos, convert             ,  _           );
      def(Bytecodes::_d2l                 , ____|____|____|____, dtos, ltos, convert             ,  _           );
      def(Bytecodes::_d2f                 , ____|____|____|____, dtos, ftos, convert             ,  _           );
      def(Bytecodes::_i2b                 , ____|____|____|____, itos, itos, convert             ,  _           );
      def(Bytecodes::_i2c                 , ____|____|____|____, itos, itos, convert             ,  _           );
      def(Bytecodes::_i2s                 , ____|____|____|____, itos, itos, convert             ,  _           );
      def(Bytecodes::_lcmp                , ____|____|____|____, ltos, itos, lcmp                ,  _           );
      def(Bytecodes::_fcmpl               , ____|____|____|____, ftos, itos, float_cmp           , -1           );
      def(Bytecodes::_fcmpg               , ____|____|____|____, ftos, itos, float_cmp           ,  1           );
      def(Bytecodes::_dcmpl               , ____|____|____|____, dtos, itos, double_cmp          , -1           );
      def(Bytecodes::_dcmpg               , ____|____|____|____, dtos, itos, double_cmp          ,  1           );
      def(Bytecodes::_ifeq                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , equal        );
      def(Bytecodes::_ifne                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , not_equal    );
      def(Bytecodes::_iflt                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , less         );
      def(Bytecodes::_ifge                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , greater_equal);
      def(Bytecodes::_ifgt                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , greater      );
      def(Bytecodes::_ifle                , ubcp|____|clvm|____, itos, vtos, if_0cmp             , less_equal   );
      def(Bytecodes::_if_icmpeq           , ubcp|____|clvm|____, itos, vtos, if_icmp             , equal        );
      def(Bytecodes::_if_icmpne           , ubcp|____|clvm|____, itos, vtos, if_icmp             , not_equal    );
      def(Bytecodes::_if_icmplt           , ubcp|____|clvm|____, itos, vtos, if_icmp             , less         );
      def(Bytecodes::_if_icmpge           , ubcp|____|clvm|____, itos, vtos, if_icmp             , greater_equal);
      def(Bytecodes::_if_icmpgt           , ubcp|____|clvm|____, itos, vtos, if_icmp             , greater      );
      def(Bytecodes::_if_icmple           , ubcp|____|clvm|____, itos, vtos, if_icmp             , less_equal   );
      def(Bytecodes::_if_acmpeq           , ubcp|____|clvm|____, atos, vtos, if_acmp             , equal        );
      def(Bytecodes::_if_acmpne           , ubcp|____|clvm|____, atos, vtos, if_acmp             , not_equal    );
      def(Bytecodes::_goto                , ubcp|disp|clvm|____, vtos, vtos, _goto               ,  _           );
      def(Bytecodes::_jsr                 , ubcp|disp|____|____, vtos, vtos, jsr                 ,  _           ); // result is not an oop, so do not transition to atos
      def(Bytecodes::_ret                 , ubcp|disp|____|____, vtos, vtos, ret                 ,  _           );
      def(Bytecodes::_tableswitch         , ubcp|disp|____|____, itos, vtos, tableswitch         ,  _           );
      def(Bytecodes::_lookupswitch        , ubcp|disp|____|____, itos, itos, lookupswitch        ,  _           );
      def(Bytecodes::_ireturn             , ____|disp|clvm|____, itos, itos, _return             , itos         );
      def(Bytecodes::_lreturn             , ____|disp|clvm|____, ltos, ltos, _return             , ltos         );
      def(Bytecodes::_freturn             , ____|disp|clvm|____, ftos, ftos, _return             , ftos         );
      def(Bytecodes::_dreturn             , ____|disp|clvm|____, dtos, dtos, _return             , dtos         );
      def(Bytecodes::_areturn             , ____|disp|clvm|____, atos, atos, _return             , atos         );
      def(Bytecodes::_return              , ____|disp|clvm|____, vtos, vtos, _return             , vtos         );
      def(Bytecodes::_getstatic           , ubcp|____|clvm|____, vtos, vtos, getstatic           , f1_byte      );
      def(Bytecodes::_putstatic           , ubcp|____|clvm|____, vtos, vtos, putstatic           , f2_byte      );
      def(Bytecodes::_getfield            , ubcp|____|clvm|____, vtos, vtos, getfield            , f1_byte      );
      def(Bytecodes::_putfield            , ubcp|____|clvm|____, vtos, vtos, putfield            , f2_byte      );
      def(Bytecodes::_invokevirtual       , ubcp|disp|clvm|____, vtos, vtos, invokevirtual       , f2_byte      );
      def(Bytecodes::_invokespecial       , ubcp|disp|clvm|____, vtos, vtos, invokespecial       , f1_byte      );
      def(Bytecodes::_invokestatic        , ubcp|disp|clvm|____, vtos, vtos, invokestatic        , f1_byte      );
      def(Bytecodes::_invokeinterface     , ubcp|disp|clvm|____, vtos, vtos, invokeinterface     , f1_byte      );
      def(Bytecodes::_invokedynamic       , ubcp|disp|clvm|____, vtos, vtos, invokedynamic       , f1_byte      );
      def(Bytecodes::_new                 , ubcp|____|clvm|____, vtos, atos, _new                ,  _           );
      def(Bytecodes::_newarray            , ubcp|____|clvm|____, itos, atos, newarray            ,  _           );
      def(Bytecodes::_anewarray           , ubcp|____|clvm|____, itos, atos, anewarray           ,  _           );
      def(Bytecodes::_arraylength         , ____|____|____|____, atos, itos, arraylength         ,  _           );
      def(Bytecodes::_athrow              , ____|disp|____|____, atos, vtos, athrow              ,  _           );
      def(Bytecodes::_checkcast           , ubcp|____|clvm|____, atos, atos, checkcast           ,  _           );
      def(Bytecodes::_instanceof          , ubcp|____|clvm|____, atos, itos, instanceof          ,  _           );
      def(Bytecodes::_monitorenter        , ____|disp|clvm|____, atos, vtos, monitorenter        ,  _           );
      def(Bytecodes::_monitorexit         , ____|____|clvm|____, atos, vtos, monitorexit         ,  _           );
      def(Bytecodes::_wide                , ubcp|disp|____|____, vtos, vtos, wide                ,  _           );
      def(Bytecodes::_multianewarray      , ubcp|____|clvm|____, vtos, atos, multianewarray      ,  _           );
      def(Bytecodes::_ifnull              , ubcp|____|clvm|____, atos, vtos, if_nullcmp          , equal        );
      def(Bytecodes::_ifnonnull           , ubcp|____|clvm|____, atos, vtos, if_nullcmp          , not_equal    );
      def(Bytecodes::_goto_w              , ubcp|____|clvm|____, vtos, vtos, goto_w              ,  _           );
      def(Bytecodes::_jsr_w               , ubcp|____|____|____, vtos, vtos, jsr_w               ,  _           );
    
      // wide Java spec bytecodes
      def(Bytecodes::_iload               , ubcp|____|____|iswd, vtos, itos, wide_iload          ,  _           );
      def(Bytecodes::_lload               , ubcp|____|____|iswd, vtos, ltos, wide_lload          ,  _           );
      def(Bytecodes::_fload               , ubcp|____|____|iswd, vtos, ftos, wide_fload          ,  _           );
      def(Bytecodes::_dload               , ubcp|____|____|iswd, vtos, dtos, wide_dload          ,  _           );
      def(Bytecodes::_aload               , ubcp|____|____|iswd, vtos, atos, wide_aload          ,  _           );
      def(Bytecodes::_istore              , ubcp|____|____|iswd, vtos, vtos, wide_istore         ,  _           );
      def(Bytecodes::_lstore              , ubcp|____|____|iswd, vtos, vtos, wide_lstore         ,  _           );
      def(Bytecodes::_fstore              , ubcp|____|____|iswd, vtos, vtos, wide_fstore         ,  _           );
      def(Bytecodes::_dstore              , ubcp|____|____|iswd, vtos, vtos, wide_dstore         ,  _           );
      def(Bytecodes::_astore              , ubcp|____|____|iswd, vtos, vtos, wide_astore         ,  _           );
      def(Bytecodes::_iinc                , ubcp|____|____|iswd, vtos, vtos, wide_iinc           ,  _           );
      def(Bytecodes::_ret                 , ubcp|disp|____|iswd, vtos, vtos, wide_ret            ,  _           );
      def(Bytecodes::_breakpoint          , ubcp|disp|clvm|____, vtos, vtos, _breakpoint         ,  _           );
    
      // JVM bytecodes
      def(Bytecodes::_fast_agetfield      , ubcp|____|____|____, atos, atos, fast_accessfield    ,  atos        );
      def(Bytecodes::_fast_bgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );
      def(Bytecodes::_fast_cgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );
      def(Bytecodes::_fast_dgetfield      , ubcp|____|____|____, atos, dtos, fast_accessfield    ,  dtos        );
      def(Bytecodes::_fast_fgetfield      , ubcp|____|____|____, atos, ftos, fast_accessfield    ,  ftos        );
      def(Bytecodes::_fast_igetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );
      def(Bytecodes::_fast_lgetfield      , ubcp|____|____|____, atos, ltos, fast_accessfield    ,  ltos        );
      def(Bytecodes::_fast_sgetfield      , ubcp|____|____|____, atos, itos, fast_accessfield    ,  itos        );
    
      def(Bytecodes::_fast_aputfield      , ubcp|____|____|____, atos, vtos, fast_storefield ,   atos        );
      def(Bytecodes::_fast_bputfield      , ubcp|____|____|____, itos, vtos, fast_storefield ,   itos        );
      def(Bytecodes::_fast_cputfield      , ubcp|____|____|____, itos, vtos, fast_storefield  ,  itos        );
      def(Bytecodes::_fast_dputfield      , ubcp|____|____|____, dtos, vtos, fast_storefield  ,  dtos        );
      def(Bytecodes::_fast_fputfield      , ubcp|____|____|____, ftos, vtos, fast_storefield  ,  ftos        );
      def(Bytecodes::_fast_iputfield      , ubcp|____|____|____, itos, vtos, fast_storefield  ,  itos        );
      def(Bytecodes::_fast_lputfield      , ubcp|____|____|____, ltos, vtos, fast_storefield  ,  ltos        );
      def(Bytecodes::_fast_sputfield      , ubcp|____|____|____, itos, vtos, fast_storefield  ,  itos        );
    
      def(Bytecodes::_fast_aload_0        , ____|____|____|____, vtos, atos, aload               ,  0           );
      def(Bytecodes::_fast_iaccess_0      , ubcp|____|____|____, vtos, itos, fast_xaccess        ,  itos        );
      def(Bytecodes::_fast_aaccess_0      , ubcp|____|____|____, vtos, atos, fast_xaccess        ,  atos        );
      def(Bytecodes::_fast_faccess_0      , ubcp|____|____|____, vtos, ftos, fast_xaccess        ,  ftos        );
    
      def(Bytecodes::_fast_iload          , ubcp|____|____|____, vtos, itos, fast_iload          ,  _       );
      def(Bytecodes::_fast_iload2         , ubcp|____|____|____, vtos, itos, fast_iload2         ,  _       );
      def(Bytecodes::_fast_icaload        , ubcp|____|____|____, vtos, itos, fast_icaload        ,  _       );
    
      def(Bytecodes::_fast_invokevfinal   , ubcp|disp|clvm|____, vtos, vtos, fast_invokevfinal   , f2_byte      );
    
      def(Bytecodes::_fast_linearswitch   , ubcp|disp|____|____, itos, vtos, fast_linearswitch   ,  _           );
      def(Bytecodes::_fast_binaryswitch   , ubcp|disp|____|____, itos, vtos, fast_binaryswitch   ,  _           );
    
      def(Bytecodes::_fast_aldc           , ubcp|____|clvm|____, vtos, atos, fast_aldc           ,  false       );
      def(Bytecodes::_fast_aldc_w         , ubcp|____|clvm|____, vtos, atos, fast_aldc           ,  true        );
    
      def(Bytecodes::_return_register_finalizer , ____|disp|clvm|____, vtos, vtos, _return       ,  vtos        );
    
      def(Bytecodes::_invokehandle        , ubcp|disp|clvm|____, vtos, vtos, invokehandle        , f1_byte      );
    
      def(Bytecodes::_shouldnotreachhere   , ____|____|____|____, vtos, vtos, shouldnotreachhere ,  _           );
      // platform specific bytecodes
      pd_initialize();
    
      _is_initialized = true;
    }

    运行模式

    • 字节码解释器

    早期JVM采用的就是该模式,字节码解释器模式下,逐条指令执行,代码运行速度相对较慢,因为它的编译条件相对宽松,编译所需要的信息较少,因此编译速度快,可以通过虚拟机参数-Xint设置;

    • 模板解释器

    模板解释器执行速度很快,但是因为它需要将代码全部编译成本地代码,因此它编译需要收集的信息比较多,所以编译速度比较慢,可以通过虚拟机参数-Xcomp设置

    • 字节码解释器 + 模板解释器

    可以通过虚拟机参数-Xmixed设置,通过上面的分析,可能大家会觉得,既然模板解释器执行速度快,那JVM肯定采用模板解释器来执行,其实并不是。在说明原因之前,先提一个概念:热点代码。

    热点代码

    顾名思义,热点代码就是经常被执行的代码,虚拟机会判断一个方法或者一个代码块的运行特别频繁,就会把这些代码认定为热点代码,热点代码主要包含两类代码:被多次调用的方法、被多次执行的循环体,多次调用的方法很好理解,多次执行的循环体是指一个方法内如果存在一个循环体,虽然这个方法可能只是被执行一次或者寥寥几次,但是这个循环体却被多次执行,主要体现在循环次数,那么这个循环体也是热点代码。

    热点探测

    虚拟机是如何判断一段代码是不是热点代码呢,这就需要有一套热点探测的机制了,JVM主要有两种热点探测机制:

    1)基于采样的热点探测

    虚拟机定期检查各个线程的栈顶,如果发现某个方法频繁出入栈顶,就将该方法认定为热点代码;

    2)基于计数器的热点探测

    为每个代码块(方法或者循环体)维护一个计数器,每执行一次代码块,计数器+1,当计数器值达到阈值时,就认定该代码块为热点代码,HotSpot采用的就是这种方式。Client 编译器模式下,该阈值是 1500;Server编译器模式下,该阈值是10000,也可以通过虚拟机参数-XX:CompileThreshold设定。不过热点代码的地位不是一直保持热度的,当该代码块一段时间没有执行或者执行次数无法达到阈值时,热度就会衰减一半,比如某个热点代码块的热点值是10000,热度衰减后,就变成了5000。

    热点代码缓存

    热点代码缓存在方法区中的一块内存空间,这块内存空间叫Code Cache。可以通过命令java -client -XX:+PrintFlagsFinal -version | grep CodeCache查看Code Cache默认大小:

    介绍了热点代码之后,再来说说现在的JVM为什么采用混合执行模式,试想一下,假如采用了模板解释器模式,不管三七二十一,直接对所有代码都编译成硬编码,可能大部分代码都只是执行一次或者少数几次,这样编译全部代码就造成了性能浪费,而且对于现在动辄几百兆甚至几个G的应用,如果采用纯模板解释器,应用的启动就会很慢,可能启动后很长时间都无法使用,这也无法接受,因此采用混合模式,在应用启动后的前期,使用字节码解释器解释执行,当程序运行了一段时间之后,JVM就能够根据代码块的执行次数判断哪些代码属于热点代码,就会将热点代码保存在方法区,下次执行的时候就使用模板解释器编译执行,就无需再编译了,从而提升了性能。现在的虚拟机默认都是采用混合模式,可以通过java -version命令查看:

     

    三种运行模式性能比较

    通过一段代码来测试一下:

    public static void main(String[] args) {
        long star = System.currentTimeMillis();
        operation(100000);
        long end = System.currentTimeMillis();
        System.out.println(end - star + " ms");
    }
    
    public static void operation(int n){
        int num=0;
        boolean sign;
        for(int i = 2; i < n; i++){
            if(i % 2 == 0 && i != 2){
                continue;
            }
            sign = true;
            for (int j = 2; j < i; j++){
                if(i % j == 0){
                    sign = false;
                    break;
                }
            }
            if (sign){
                num++;
            }
        }
    }

    以上代码通过一段算术运算,分别设置参数-Xint、-Xcomp、-Xmixed然后运行,执行结果分别为:6143ms、1961ms、2015ms,可以看到,-Xint耗时明显最多,所以它的执行性能最差,编译执行和混合执行模式在这段代码上性能差不多,如果当一个程序非常大的话,可能模板执行器模式耗时就会比混合模式更久些,因为编译需要更长的时间。

     

    即时编译器

    前面介绍的模板解释器所执行的本地代码就是即时编译器编译生成的,因此所谓的混合运行模式(-Xmixed)其实就是字节码解释器和即时编译器混合使用的方式。所以当使用解释执行模式时(-Xint),则只使用了字节码解释器,即时编译器并没有用;当使用编译模式执行时,这时候即时编译器就会起作用(编译后的本地代码给模板解释器使用)。常见的即时编译器主要有两种:C1编译器(客户端编译器)和C2编译器(服务端编译器),当虚拟机采用client模式工作时,使用的是C1即时编译器,采用server模式工作时,使用的是C2即时编译器,默认是server模式,见上文中java -version执行结果的截图,也可以使用-client和-server参数设置。

    C1编译器

    client模式下的即时编译器,它触发的条件相对宽松,需要收集的信息较少,它会对字节码在编译的时候进行浅程度的优化,因此相对于C2编译器,它的编译速度更快,但是执行速度相对较慢

    C2编译器

    server模式下的即时编译器,触发的条件比较苛刻,它会对代码进行更深层次的优化,因此它的编译程度比较深,编译后的代码质量更高,随之带来的也是更高的编译耗时,但是它的执行速度更快。

    混合编译

    由于即时编译器编译本地代码需要占用程序运行时间, 通常要编译出优化程度越高的代码, 所花费的时间便会越长; 而且想要编译出优化程度更高的代码, 解释器可能还要替编译器收集性能监控信息, 这对解释执行阶段的速也有所影响。 为了在程序启动响应速度与运行效率之间达到最佳平衡,HotSpot虚拟机在编译子系统中加入了分层编译的功能(即混合编译),混合编译根据编译器编译、 优化的规模与耗时, 划分出不同的编译层次, 其中包括:

    • 第0层。 程序纯解释执行, 并且解释器不开启性能监控功能(Profiling) 。
    • 第1层。 使用客户端编译器将字节码编译为本地代码来运行, 进行简单可靠的稳定优化, 不开启性能监控功能。
    • 第2层。 仍然使用客户端编译器执行, 仅开启方法及回边次数统计等有限的性能监控功能。
    • 第3层。 仍然使用客户端编译器执行, 开启全部性能监控, 除了第2层的统计信息外, 还会收集如分支跳转、 虚方法调用版本等全部的统计信息。
    • 第4层。 使用服务端编译器将字节码编译为本地代码, 相比起客户端编译器, 服务端编译器会启用更多编译耗时更长的优化, 还会根据性能监控信息进行一些不可靠的激进优化。

    即时编译器触发条件

    上面其实已经在热点代码中介绍过了,当方法执行次数达到指定的阈值时,就会触发即时编译,同时也可以指定,即时编译器编译的其实就是热点代码,只有存在热点代码时,即时编译器才会起作用。

    当虚拟机执行到一个方法的时候,会先判断该方法是不是已经编译过(判断是否存在该方法对应的本地代码),如果存在,则直接执行该本地代码;如果不存在,才会将该方法的热点计数器+1,然后判断是否达到设定的阈值,一旦达到了阈值,则会触发即时编译器进行编译。所以当一个代码块一旦触发过即时编译,那么以后每次执行它都是执行它被编译后的本地机器码,也即采用的是编译执行模式(-Xcomp)。

    需要注意的是,当即时编译器进行编译时,执行引擎并不会等待其编译完成,而是直接使用字节码解释器解释执行,当即时编译器编译完成后,下一次再执行该方法的时候就会执行已经编

    译后的本地代码了,这样做的目的是尽可能的提高执行效率,防止同步等待造成性能浪费。

    即时编译器是如何实现的?

    如果触发了即时编译,那么就会产生一个即时编译任务,将这个任务放到队列里,然后由VM_THREAD线程(linux系统)从这个队列里取出任务执行。而这个VM_THREAD线程可能会有多个,可以通过命令java -client -XX:+PrintFlagsFinal -version | grep CICompilerCount 查看有多少个线程(默认4个),可以通过参数:-XX:CICompilerCount=XXX进行设置,从而对即时编译器进行调优。

     

    javac编译器与即时编译器

    本文中所说的“编译”都是指即时编译器的编译过程,它与javac将java代码编译成字节码是完全不同的。java编译器是由Java代码实现,是Java虚拟机之外的编译器,又称前端编译器;而即时编译器完全是在Java虚拟机内部进行,由C++代码实现,又称后端编译器,它主要是运行时将字节码编译成本地机器码。由于Java程序要运行的话,需要先经过javac将.java文件编译成.class文件,这个过程在虚拟机外部进行;然后由执行引擎执行字节码,而执行引擎执行字节码主要是由解释器+即时编译器搭配进行,这个过程在虚拟机内部进行,基于此,我们常说“Java是半编译半解释型的语言”。

     

     

    展开全文
  • JVM的解释器和即时编译器JIT

    千次阅读 2022-03-24 20:16:58
    1 什么是Java编译器   Java编译器:将Java源文件(.java...2 JVM内的解释器和即时编译器器   什么是机器码?   机器码就是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合。   而解释器和即时编.
  • 在前面两篇文章讲述了即时编译器的两种特殊优化技术:方法内联和逃逸分析,其中基于逃逸分析结果又有三种优化方式:同步消除、栈上分配以及标量替换。 除此之外,即时编译器还有很多优化手段,其中有不少经典编译器...
  • Java中的即时编译器

    2021-03-05 19:47:55
    这周学习了关于java即时编译器的内容,发现了不少虚拟机的秘密,整理出来分享给大家。本篇内容主要是参考了《深入理解Java虚拟机》第11章,整理了其中的核心内容,想看书了解即时编译器的也可(书下载链接:...
  • 学习JVM相关的知识,必然绕不开即时编译器,因为它太重要了。了解了它的基本原理及优化手段,在编程过程中可以让我们有种打开任督二脉的感觉。比如,很多朋友在面试当中还会遇到这样的问题:Java是基于编译执行还是...
  • 前情提要,Java 既有解释执行,也有编译执行,为了解决解释器的性能瓶颈问题,优化 Java 的性能,引入了即时编译器,大幅度的提高运行效率。那么,即时编译器又是何方神圣呢,接下来让我们一起去揭开它神秘的面纱! ...
  • 即时编译器

    2019-07-04 09:51:45
    引入多个即时编译器,是为了在编译时间和生成代码的执行效率之间进行取舍。 C1 又叫做 Client 编译器,面向的是对启动性能有要求的客户端 GUI 程序,采用的优化手段相对简单,因此编 译时间较短。 C2 又叫做 ...
  • 讲述了JIT中的Trampoline的实现,对于了解JIT很有帮助.
  • Java - JIT即时编译器

    2021-10-25 15:43:37
    Java - JIT即时编译器原理解析前言一. JIT即时编译器 前言 我们知道,Java有着“一次编译,处处运行”的特性,会将编译阶段分成两部分: 前端编译:由javac编译成字节码。 后端编译:由解释器将字节码解释为机器码...
  • Java真的是一门编译型的语言吗——即时编译器JIT 如有错误请大佬指正 JIT是什么 JIT(Just-in-Time,实时编译)一直是Java语言的灵魂特性之一。 让我们回忆一下Java程序是如何运行的 我们知道编程语言根据编译及运行...
  • 这是一个用 Rust 编写的 Javascript 词法分析器、解析器和即时编译器。 目前,它支持某些语言。 文档 贡献 如果您想贡献,只需提交包含更改的拉取请求。 如果它通过了 travis 并且相当干净和合理,它将被合并。 查看...
  • 使用NASM测试Pawn的即时编译器。该代码基于原始的 Pawn示例。在提供了一个示例脚本。不过,您需要使用自己的Pawn 4编译器。 经过测试的编译器 基于使用《 Pawn实施者指南》 PDF的Borland编译器版本,我可以找到早在...
  • 即时编译器分为C1及即时编译器和C2即时编译器,C1即时编译器采集程序的运行状态数据,进行编译优化,C2即时编译器在C1即时编译器上层彻底进行优化,C2即时编译器的优化性能高于C1即时编译器,C1即时编译器性能高于...
  • rejit是一个简单的正则表达式即时编译器,适用于 Python。 该项目旨在用于教育和实验目的。 描述 rejit支持正则表达式,它描述了正则语言,就像在。 这意味着rejit缺乏像反向引用这样的高级正则表达式功能,因为表达...
  • JVM即时编译器

    2021-07-12 08:21:55
    为了提高热点代码的执行效率,在运行时,虚拟机会把这些代码编译成本地代码,并以各种手段尽可能的进行代码优化,运行时完成这个任务的后端编译器被称为即时编译器。 解释器与编译器 当程序需要快速启动和执行的时候...
  • 传统的编译器需要借助数据流分析(具体的优化叫reaching defnition),从后至前依次确认哪些变量的值被覆盖不过,如果借助了 SSA IR,编译器则可
  • 程序解释运行后,JIT编译器逐渐发挥作用; 编译成本地代码,提高执行效率; 但占用程序运行时间、内存等资源; 热点代码会被jit编译编译成本地代码,直接和操作系统cpu交互执行,不需要经过解释器去执行了。效率大大...
  • java虚拟机讲解第八篇1、讲解内容逻辑梳理图:逻辑图为百度脑图,以下为原图连接:http://naotu.baidu.com/file/17c1cd2e634918a57df6a0ec9d983317?token=7743a63b2c17f9372、内容详细讲解:2.1、即时编译器:程序在...
  • 这个过程说得 专业一点,就称为编译(Compile),而负责编译的程序称为编译器(Compiler)。 解释型 “编译”和“解释”的确都有“翻译”的意思,它们的区别则在于翻译的时机安排不大一样。编译型:事先一次把...
  • Java即时编译器原理解析及实践一、序言二、Java的执行过程1、Java的执行过程整体可以分为两个部分2、怎么样才会被认为是热点代码3、JVM中集成了两种编译器4、JDK7前后C1编译器和C2编译器使用的区别5、分层编译即时...
  • 提出一种应用于即时编译器中的代码生成优化技术――代码消极生成机制,结合Intel微处理器研究院的XORP虚拟机对该项技术进行了阐述。介绍了XORP中实现的其他轻量级优化算法,并采用EEMBC对不同虚拟机和不同优化进行了...
  • luaJIT(lua即时编译器)

    2012-06-03 11:54:30
    lua的协程切换效率都是百万级别,luaJIT 2.0的性能更是牛叉,切换效率是原生lua的4倍,达到千万级别。
  • 我之前一篇笔记里有举例子说明HotSpot就算在-Xcomp模式下仍然可能(而且非常可能)用解释器来执行方法的:htt书中第250页这段文字不幸几乎每句都有错⋯
  • 21丨深入JVM即时编译器JIT,优化Java编译.html
  • JIT即时编译器(C1和C2)

    2021-04-16 15:52:53
    上一篇文章我们已经讲述了JIT编译器的基本原理,今天我们看一下HotSpot虚拟机中具体的编译器。1. Client Compiler(C1编译器)C1编译器启动速度快,但是性能相比较Server Compiler相对来说会差一些,下面我们主要看...
  • - 第1章 走近Java- 1.5 展望Java技术的未来1.5.2 新一代即时编译器对需要长时间运行的应用来说,由于经过充分预热,热点代码会被HotSpot的
  • Easy :: jit:C ++的即时编译器 关于 Easy :: jit是编译器辅助的库,可为C ++代码生成简单的即时代码。 会谈 建造 首先,安装clang和LLVM。 apt install llvm-6.0-dev llvm-6.0-tools clang-6.0 然后,配置并编译...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 60,416
精华内容 24,166
关键字:

即时编译器