即时编译(JIT just in time,默认是开启的)是一项用来提升应用程序运行效率的技术。通常而言,代码会先被 Java 虚拟机解释执行,之后反复执行的热点代码则会被即时编译成为机器码,直接运行在底层硬件之上。

  HotSpot 虚拟机包含多个即时编译器 C1、C2 和 Graal(实验性质)。其中,Graal 是一个实验性质的即时编译器,可以通过参数 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler 启用,并且替换 C2。

1. 分层编译模式  

  在 Java 7 以前,我们需要根据程序的特性选择对应的即时编译器。对于执行时间较短的,或者对启动性能有要求的程序,我们采用编译效率较快的 C1,对应参数 -client。对于执行时间较长的,或者对峰值性能有要求的程序,我们采用生成代码执行效率较快的 C2,对应参数 -server。

  Java 7 引入了分层编译(对应参数 -XX:+TieredCompilation)的概念,综合了 C1 的启动性能优势和 C2 的峰值性能优势。分层编译将 Java 虚拟机的执行状态分为了五个层次。为了方便阐述,我用“C1 代码”来指代由 C1 生成的机器码,“C2 代码”来指代由 C2 生成的机器码。五个层级分别是:

    0. 解释执行;

  1. 执行不带 profiling 的 C1 代码;
  2. 执行仅带方法调用次数以及循环回边执行次数 profiling 的 C1 代码;
  3. 执行带所有 profiling 的 C1 代码;(除level 2中的profiling外还包括branch(针对分支跳转字节码)及receiver type(针对成员方法调用或类检测,如checkcast,instnaceof,aastore字节码)的profiling)
  4. 执行 C2 代码。

  通常情况下,C2 代码的执行效率要比 C1 代码的高出 30% 以上。然而,对于 C1 代码的三种状态,按执行效率从高至低则是 1 层 > 2 层 > 3 层。其中 1 层的性能比 2 层的稍微高一些,而 2 层的性能又比 3 层高出 30%。这是因为 profiling 越多,其额外的性能开销越大。

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

上图列举了4种编译模式(非全部)。

  • 通常情况下,一个方法先被解释执行(level 0),然后被C1编译(level 3),再然后被得到profile数据的C2编译(level 4)。
  • 如果编译对象非常简单(trivial method--非常简单的,如方法的字节码数目比较少(如 getter/setter),而且 3 层的 profiling 没有可收集的数据),虚拟机认为通过C1编译或通过C2编译并无区别,便会直接由C1编译且不插入profiling代码(level 1)。
  • 在C1忙碌的情况下,interpreter会触发profiling,而后方法会直接被C2编译;
  • 在C2忙碌的情况下,方法则会先由C1编译并保持较少的profiling(level 2),以获取较高的执行效率(与3级相比高30%)。

2. 即时编译的触发

  Java 虚拟机是根据方法的调用次数以及循环回边的执行次数(循环体内循环代码的执行次数(即for中代码的循环的次数))来触发即时编译的。前面提到,Java 虚拟机在 0 层、2 层和 3 层执行状态时进行 profiling,其中就包含方法的调用次数和循环回边的执行次数。

  这里的循环回边是一个控制流图中的概念。在字节码中,我们可以简单理解为往回跳转的指令。(注意,这并不一定符合循环回边的定义。)

  实际上,Java 虚拟机并不会对这些计数器进行同步操作,因此收集而来的执行次数也并非精确值。不管如何,即时编译的触发并不需要非常精确的数值。只要该数值足够大,就能说明对应的方法包含热点代码。

3. Profiling优化

3.1 基于分支profile的优化

例:

 1 public static int foo(boolean f, int in) {
2 int v;
3 if (f) {
4 v = in;
5 } else {
6 v = (int) Math.sin(in);
7 }
8
9 if (v == in) {
10 return 0;
11 } else {
12 return (int) Math.cos(v);
13 }
14 }
15 // 编译而成的字节码:
16 public static int foo(boolean, int);
17 Code:
18 0: iload_0
19 1: ifeq 9
20 4: iload_1
21 5: istore_2
22 6: goto 16
23 9: iload_1
24 10: i2d
25 11: invokestatic java/lang/Math.sin:(D)D
26 14: d2i
27 15: istore_2
28 16: iload_2
29 17: iload_1
30 18: if_icmpne 23
31 21: iconst_0
32 22: ireturn
33 23: iload_2
34 24: i2d
35 25: invokestatic java/lang/Math.cos:(D)D
36 28: d2i
37 29: ireturn

  假设应用程序调用该方法时,所传入的 boolean 值皆为 true。那么,偏移量为 1 以及偏移量为 18 的条件跳转指令所对应的分支 profile 中,跳转的次数都为 0。

  C2 可以根据这两个分支 profile 作出假设,在接下来的执行过程中,这两个条件跳转指令仍旧不会发生跳转。基于这个假设,C2 便不再编译这两个条件跳转语句所对应的 false 分支了。

  我们暂且不管当假设错误的时候会发生什么,先来看一看剩下来的代码。经过“剪枝”之后,在第二个条件跳转处,v 的值只有可能为所输入的 int 值。因此,该条件跳转可以进一步被优化掉。最终的结果是,在第一个条件跳转之后,C2 代码将直接返回 0。

3.2 基于类型profile的优化

例:

 1 public static int hash(Object in) {
2 if (in instanceof Exception) {
3 return System.identityHashCode(in);
4 } else {
5 return in.hashCode();
6 }
7 }
8 // 编译而成的字节码:
9 public static int hash(java.lang.Object);
10 Code:
11 0: aload_0
12 1: instanceof java/lang/Exception
13 4: ifeq 12
14 7: aload_0
15 8: invokestatic java/lang/System.identityHashCode:(Ljava/lang/Object;)I
16 11: ireturn
17 12: aload_0
18 13: invokevirtual java/lang/Object.hashCode:()I
19 16: ireturn

  在我们的例子中,instanceof 指令的类型 profile 仅包含 Integer。根据这个信息,即时编译器可以假设,在接下来的执行过程中,所输入的 Object 对象仍为 Integer 实例。

  因此,生成的代码将测试所输入的对象的动态类型是否为 Integer。如果是的话,则继续执行接下来的代码。(该优化源自 Graal,采用 C2 可能无法复现。)

  然后,即时编译器会采用和第一个例子中一致的针对分支 profile 的优化,以及对方法调用的条件去虚化内联。

  我会在接下来的篇章中详细介绍内联,这里先说结果:生成的代码将测试所输入的对象动态类型是否为 Integer。如果是的话,则执行 Integer.hashCode() 方法的实质内容,也就是返回该 Integer 实例的 value 字段。

3.3 去优化

  和基于分支 profile 的优化一样,基于类型 profile 的优化同样也是作出假设,从而精简控制流以及数据流。这两者的核心都是假设。

  对于分支 profile,即时编译器假设的是仅执行某一分支;对于类型 profile,即时编译器假设的是对象的动态类型仅为类型 profile 中的那几个。

  那么,当假设失败的情况下,程序将何去何从?

  Java 虚拟机给出的解决方案便是去优化,即从执行即时编译生成的机器码切换回解释执行。

  在生成的机器码中,即时编译器将在假设失败的位置上插入一个陷阱(trap)。该陷阱实际上是一条 call 指令,调用至 Java 虚拟机里专门负责去优化的方法。与普通的 call 指令不一样的是,去优化方法将更改栈上的返回地址,并不再返回即时编译器生成的机器码中。

  在上面的程序控制流图中,我画了很多红色方框的问号。这些问号便代表着一个个的陷阱。一旦踏入这些陷阱,便将发生去优化,并切换至解释执行。

JVM-即时编译的更多相关文章

  1. JVM即时编译(JIT)

    Java解释执行过程: 代码装入-代码校验-代码执行 Java字节码的执行方式分为两种:即使编译方式和解释执行方式.即时编译是值解释器先将字节码编译成机器码,然后执行该机器码.解释执行的方式是指解释器 ...

  2. java 笔记(1)-—— JVM基础,内存数据,内存释放,垃圾回收,即时编译技术JIT,高精度类型

    1.java中5个存放数据的地方: (1).寄存器(Registers):位于CPU内部,是速度最快的存储区,但是数量和容量有限.在java中不能直接操作寄存器. (2).栈(Stack):栈位于通用 ...

  3. 转:什么是即时编译(JIT)!?OpenJDK HotSpot VM剖析

    重点 应用程序可以选择一个适当的即时编译器来进行接近机器级的性能优化. 分层编译由五层编译构成. 分层编译提供了极好的启动性能,并指导编译的下一层编译器提供高性能优化. 提供即时编译相关诊断信息的JV ...

  4. Eclipse Java class修改后的即时编译

    通常情况下,修改了java文件,需要重启eclipse.但是myeclipse可以不用. 其实即时编译早就有了,通过简单配置javaRebel配置,可以达到修改java文件后不重启eclipse. 注 ...

  5. 即时编译和打包您的 Groovy 脚本(转)

    在本文中将会涉及到: 使用 CliBuilder 来实现对命令行选项的支持,脚本执行时所需要的参数将通过命令行选项的方式传递. 使用 GroovyClassLoader 加载 Groovy class ...

  6. JIT——即时编译的原理

     介绍 java 作为静态语言十分特殊,他需要编译,但并不是在执行之前就编译为本地机器码. 所以,在谈到 java的编译机制的时候,其实应该按时期,分为两个部分.一个是 javac指令 将java源码 ...

  7. 《深入理解java虚拟机》学习笔记之虚拟机即时编译详解

    郑重声明:本片博客是学习<深入理解java虚拟机>一书所记录的笔记,内容基本为书中知识. Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块 ...

  8. 即时编译(JIT)

    即时编译(JIT : just-in-time compilation): 指计算机领域里,即时编译也被成为动态翻译,是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技术 即时编译 ...

  9. Java 面试-即时编译( JIT )

    当我们在写代码时,一个方法内部的行数自然是越少越好,这样逻辑清晰.方便阅读,其实好处远不止如此,通过即时编译,甚至可以提高执行时的性能,今天就让我们好好来了解一下其中的原理. 简介 当 JVM 的初始 ...

  10. JAVA虚拟机25---编译器,解释器,JAVA中的即时编译

    https://www.cnblogs.com/somefuture/p/14272221.html 1.简介 编译器:是一种计算机程序,负责把一种编程语言编写的源码转换成另外一种计算机代码,后者往往 ...

随机推荐

  1. Redis核心技术与实践 02 | 数据结构:快速的Redis有哪些慢操作?

    原文地址:https://time.geekbang.org/column/article/268262 博客地址:http://njpkhuan.cn/archives/redis-he-xin-j ...

  2. Java并发(十三)----共享存在的问题

    1.小故事 老王(操作系统)有一个功能强大的算盘(CPU),现在想把它租出去,赚一点外快 小南.小女(不同的线程)来使用这个算盘来进行一些计算,并按照时间给老王支付费用 但小南不能一天24小时使用算盘 ...

  3. TypeScript: 類型 'HTMLElement | null' 不可指派給類型 'HTMLElement'。 類型 'null' 不可指派給類型 'HTMLElement'

    报错截图 解决方案 在结尾添加叹号 // 获取页面中food的元素并将其赋值给element this.element = document.getElementById('food')!;

  4. NOIP2022 题解

    终于有机会补NOIP的题了 T1 考虑枚举 C 与 F 的纵列 考虑预处理出每个点最左边和最下边可以延伸到哪 之后枚举列,然后对行做类似于扫描线的操作,统计有多少可行的 "第一横行" ...

  5. [oracle]使用impdp导入数据时卡在视图

    前言 oracle 19c使用impdp的时候卡在导入视图的地方一点不动,也没啥提示.根据网上资料,oracle 19在导入视图的时候会有bug. 步骤 查看导入任务 sqlplus / as sys ...

  6. Python类型提示

    摘自:Python 类型提示简介 - FastAPI (tiangolo.com) 快速入门 类型提示用于声明一个变量的类型,在Python 3.6+版本的时候引入. 示例: def get_full ...

  7. Redis从入门到放弃(10):分布式锁

    在分布式系统中,实现对共享资源的安全访问是一个关键问题.Redis作为一种高性能的内存数据库,提供了多种方式来实现分布式锁,以解决多个节点之间对共享资源的并发访问问题. 本文将介绍五种Redis分布式 ...

  8. 知识图谱(Knowledge Graph)根本概念

    目录 知识图谱 定义 基础概念: 知识图谱构建的关键技术 知识图谱的构建 实体命名识别 知识抽取 实体统一 指代消解 知识图谱的存储 RDF和图数据库的主要特点区别 知识图谱能干什么 反欺诈 不一致性 ...

  9. *CTF和nssctf#16的wp

    *ctf2023 fcalc 分析程序 本题存在漏洞,是生活中很容易犯的错误,就是循环或者判断的时候没有注意多一还是少一,这种会发生很严重的问题.比如这个题在过滤数字的时候没有过滤掉0,所以输入0的时 ...

  10. 古早wp合集

    0x00 首先非常感谢大家阅读我的第一篇.本文章不仅仅是题解,一些细枝末节的小问题也欢迎大家一起解答. 小问题的形式如Qx:xxxxxxx? 欢迎发现小问题并讨论~~ N1nE是本人另外一个名字,目前 ...