JVM系列七(JIT 即时编译器).
一、概述
即时编译器(Just In Time Compiler),也称为 JIT 编译器,它的主要工作是把热点代码编译成与本地平台相关的机器码,并进行各种层次的优化,从而提高代码执行的效率。
那么什么是热点代码呢?我们知道虚拟机通过解释器(Interpreter)来执行字节码文件,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code)。
即时编译器编译性能的好坏、代码优化程度的高低是衡量一款商用虚拟机优秀与否的关键指标之一,它也是虚拟机最核心且最能体现技术水平的部分。
然而,程序员在开发过程中,压根不会感知到即时编译器的存在,也参与不了即时编译器的过程,所以我们对即时编译器的学习更多的是了解,明白怎么写代码才能更好的被即时编译器优化。
二、工作流程
HotSpot 虚拟机包含解释器和编译器。它们是怎么搭配工作的呢?当程序启动的时候,解释器首先发挥作用,它能直接运行字节码文件;随着时间的推移,越来越多的热点代码被编译器编译成机器码,从而获取更高的执行效率。同时,解释器还可以作为编译器激进优化时的一个“逃生门”,当编译器的激进优化手段不成立时,如加载了新类后类型继承结构出现变化等,可以通过逆优化(Deoptimization)退回到解释状态继续由解释器执行。
编译器又分为两种,C1 编译器(Client Compiler)和 C2 编译器(Server Compiler),HotSpot 虚拟机会选择哪个编译器是由虚拟机运行于 Client 模式还是 Server 模式决定的。
默认情况下,虚拟机采用解释器和一种编译器搭配的方式工作,但是在分层编译策略下,C1 编译器和 C2 编译器将会同时工作,分层编译根据编译器编译、优化的规模和耗时,划分出不同的编译层次:
- 第0层:程序解释执行,解释器不开启性能监控功能,触发 C1 编译。
- 第1层:C1 编译,将字节码编译成本地代码,进行简单、可靠的优化,如有必要解释器将开始性能监控。
- 第2层:C2 编译,将字节码编译成本地代码,启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
tips:
- 使用 “-client” 强制虚拟机运行于 Client 模式。
- 使用 “-server” 强制虚拟机运行于 Server 模式。
- 使用 “-Xint” 强制虚拟机只使用解释器执行程序,编译器不工作。
- 使用 “-Xcomp” 强制虚拟机只使用编译器执行程序,解释器作为编译器的“逃生门”。
- 使用 “-XX:+TieredCompilation” 开启分层编译。虚拟机 Server 模式下默认开启。
三、热点代码探测
热点代码分为两种:被多次调用的方法、被多次执行的循环体。多次是一个很泛的概念,那么到底什么时候才能把热点代码编译成机器码呢?HotSpot 虚拟机采用的是计数器的方式,它为每个方法(甚至是代码块)建立计数器,统计执行次数,如果执行次数达到一定的阈值,就把这部分代码编译成机器码。
探测“被多次调用的方法”的计数器称为方法调用计数器(Invocation Counter),它统计的是一个方法调用的相对次数,即同一段时间内方法被调用的次数,当超过一定的时间限度,如果该方法的计数仍然不足以让它提交给编译器编译,那么该方法的计数就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),这段时间就被称为此方法统计的半衰周期(Counter Half Life Time)。方法调用计数器的相关 JVM 参数如下:
- -XX:CompileThreshold 设置方法调用计数器的阈值,Client 模式下默认是 1500 次, Server 模式下默认是 10000 次
- -XX:UseCounterDecay 设置 true/false 来开启/关闭热度衰减,默认开启
- -XX:CounterHalfLifeTime 设置半衰期的周期,单位是秒(debug 虚拟机支持)
探测“被多次执行的循环体”的计数器称为回边计数器(Back Edge Counter),它统计的是该方法循环执行的绝对次数,没有计数热度衰减的过程。回边计数器的相关 JVM 参数如下:
- -XX:OnStackReplacePercentage OSR比率,Client 模式下默认是 933,Server 模式下默认是 140;
- -XX:InterpreterProfilePercentage 解释器监控比率,默认值是 33
- Client 模式的回边计数器阈值 = CompileThreshold * OnStackReplacePercentage/100,默认是 13995 次
- Server 模式的回边计数器阈值 = CompileThreshold * (OnStackReplacePercentage - InterpreterProfilePercentage)/100,默认是 10700 次
四、优化技术
HotSpot 的优化技术非常全面,实现起来也比较复杂,但是对于理解它们来说却显得没那么困难,我们将列举几项最有代表性的优化技术。
1. 方法内联
方法内联的重要性要优于其他优化措施,它的主要目的有两个,一是去除方法调用的成本,二是为其他优化建立良好的基础。
方法内联的行为很简单,就是把目标方法的代码“复制”到发起调用的方法之中,避免发生真实的方法调用而已。
2. 公共子表达式消除
如果一个表达式 E 已经计算过了,并且从先前的计算到现在 E 中所有变量的值都没有发生变化,那么 E 的这次出现就成为了公共子表达式。对于这种表达式,没有必要花时间再对它进行计算,只需要直接用前面计算过的表达式结果代替 E 就可以了。我们来举个例子来模拟下它的优化过程:
public static void main(String[] args) {
int a = 1;
int b = 1;
int c = 1;
int d = (c * b) * 12 + a + (a + b * c);
// 1. 提取公共子表达式
int E = c * b;
d = E * 12 + a + (a + E);
// 2. 代数化简
d = E * 13 + a * 2;
}
3. 数组边界检查消除
当我们尝试对数组越界访问的时候,Java 会向我们抛一个 java.lang.ArrayIndexOutOfBoundsException,这对软件开发者来说是一件很好的事情,即使没有专门编写防御代码,也可以避免大部分的溢出攻击,但是对虚拟机来说,意味着每一次的数组访问都带有一次隐含的条件判定操作,即数组边界检查,那么有没有办法消除这种检查呢?
虚拟机一般是在即时编译期间通过数据流分析来确定是否可以消除这种检查,比如 foo[3] 的访问,只有在编译的时候确定 3 不会超过 foo.length - 1 的值,就可以判断该次数组访问没有越界,就可以把数组边界检查消除。
4. 逃逸分析
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
如果能证明一个对象不会逃逸到方法或者线程之外,则可以为这个变量进行一些高效的优化:
1) 栈上分配
如果确定一个对象不会逃逸出方法之外,假如能使用栈上分配这个对象,那大量的对象就会随着方法的结束而自动销毁了,垃圾收集系统的压力将会小很多。然而遗憾的是,目前的 HotSpot 虚拟机还没有实现这项优化。
2)同步消除
如果确定一个对象不会被其他线程访问到,那么这个变量就不存在线程间的争抢,对这个变量实施的同步措施也可以消除掉。
3)标量替换
标量:无法被进一步分解的数据,比如原始数据类型(int、long以及 reference 类型等)
聚合量:可以被持续分解的数据,典型的就是 Java 中对象,它们还可以被分解成成员变量等。
标量替换指的是如果把一个 Java 对象拆散分解,根据程序访问的情况,将其使用到的成员变量恢复到原始类型来访问。
如果能确定一个对象不会被外部访问,并且这个对象可以被拆散的话,那程序真正执行的时候就可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。
tips:
- -XX:+DoEscapeAnalysis 手动开启/关闭逃逸分析,默认开启,C2 编译器有效
- -XX:+PrintEscapeAnalysis 查看逃逸分析的结果(debug 虚拟机支持)
- -XX:+EliminateAllocations 手动开启/关闭标量替换,默认开启
- -XX:+PrintEliminateAllocations 查看标量替换情况(debug 虚拟机支持)
- -XX:+EliminateLocks 手动开启/关闭同步消除,默认开启
五、Graal 编译器展望
Graal 编译器是 JDK10 发布的,由于这个编译器使用 Java 编写,代码清晰,又继承了许多来自 HotSpot 的服务端编译器的高质量优化技术,所以无论是科技企业还是高校研究院,都愿意在它上面研究和开发新技术。从 JDK10 起,Graal 编译器可以替换服务端编译器,成为 HotSpot 分层编译中最顶层的即时编译器。
对于 Graal 编译器的推崇离不开 JDK9 发布的 Java 虚拟机编译器接口(JVM Compiler Interface,JVMCI),JVMCI 使得 Graal 可以从 HotSpot 的代码中分离出来,使得 Graal 编译器不至于像 C2 那样跟 HotSpot 强耦合导致越来越臃肿。
JVMCI 的出现,让我们可以把一个在 HotSpot 虚拟机外部的、用 Java 语言实现的即时编译器(例如 Graal)集成到 HotSpot 中。此外,又可以绕开 HotSpot 的即时编译系统,让编译器直接为应用的类库编译出二进制机器码,将编译器当作一个提前编译器去使用(如 Jaotc)。
Graal 编译器仍处于实验室阶段,尚未商用,但未来有望代替或成为 HotSpot 的下一代技术基础。
JVM系列七(JIT 即时编译器).的更多相关文章
- jvm系列(七):jvm调优-工具篇
16年的时候花了一些时间整理了一些关于jvm的介绍文章,到现在回顾起来还是一些还没有补充全面,其中就包括如何利用工具来监控调优前后的性能变化.工具做为图形化界面来展示更能直观的发现问题,另一方面一些耗 ...
- jvm系列(七):如何优化Java GC「译」
本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...
- JVM系列之:JIT中的Virtual Call
目录 简介 Virtual Call和它的本质 Virtual Call和classic call Virtual Call优化单实现方法的例子 Virtual Call优化多实现方法的例子 总结 简 ...
- JVM系列之:JIT中的Virtual Call接口
目录 简介 最常用的接口List 多个List的调用 不一样的List调用 总结 简介 上一篇文章我们讲解了Virtual Call的定义并举例分析了Virtual Call在父类和子类中的优化. J ...
- jvm系列(七):jvm调优
转自:https://www.cnblogs.com/ityouknow/p/6437037.html 16年的时候花了一些时间整理了一些关于jvm的介绍文章,到现在回顾起来还是一些还没有补充全面,其 ...
- jvm系列(八):jvm知识点总览-高级Java工程师面试必备
在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功.对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来取胜了,但是练到高手之后 ...
- jvm系列(八):jvm知识点总览
在江湖中要练就绝世武功必须内外兼备,精妙的招式和深厚的内功,武功的基础是内功.对于武功低(就像江南七怪)的人,招式更重要,因为他们不能靠内功直接去伤人,只能靠招式,利刃上优势来取胜了,但是练到高手之后 ...
- jvm系列(四):jvm知识点总结
原文链接:http://www.cnblogs.com/ityouknow/p/6482464.html jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 ...
- jvm系列四、jvm知识点总结
原文链接:http://www.cnblogs.com/ityouknow/p/6482464.html jvm 总体梳理 jvm体系总体分四大块: 类的加载机制 jvm内存结构 GC算法 垃圾回收 ...
随机推荐
- pytorch中如何处理RNN输入变长序列padding
一.为什么RNN需要处理变长输入 假设我们有情感分析的例子,对每句话进行一个感情级别的分类,主体流程大概是下图所示: 思路比较简单,但是当我们进行batch个训练数据一起计算的时候,我们会遇到多个训练 ...
- 困扰的问题终于解决了-docker时区不正确的问题修改记
前一阵子有一台服务器,mysql的时间比北京时间晚了8个小时.我知道是时区的问题,但是不知道为什么弄成这样,宿主机没有问题,后来一看mysql的docker,时区是错的. mybatis-plus打印 ...
- ElasticSearch从不懂到会用1—安装篇
连续加班近一个多月,项目终于告一段落了,也腾出时间写一写项目中用到的东西.在这个项目中,我负责的主要是多种业务场景下的数据查询和搜索,其中搜索用到了ElasticSearch搜索引擎.下面主要围绕El ...
- 浅谈Python Django框架
1.Django简介 Python下有多款不同的 Web 框架,Django是最有代表性的一种.许多成功的网站和APP都基于Django. Django是一个开源的Web应用框架,由Python写成. ...
- python进阶之异常处理
异常处理 在代码运行时,会因为各种原因出现bug,而程序遇到bug就会中断运行,而在日常生产中程序是要长时间运行不能随意中断的.因此就需要我们提前做好异常处理. 异常 print(x) # 一般报错就 ...
- P1077 子串乘积正负分类
题目描述 给你一个序列包含 \(n\) 个元素的序列 \(a_1, a_2, \dots , a_n\) (每个元素 \(a_i \ne 0\)). 你需要计算如下两个值: 有多少对数 \((l, r ...
- H3C根桥的选举
- vue-learning:5-template-v-for
5 列表渲染的指令v-for v-for on Array / Object / String / Number v-for on template v-for on expression v-for ...
- Microsoft Ignite The Tour Beijing 记录: Learn Connect Explore
坦率的说,这是我第一次以讲师的身份参加微软的Ignite大会.同时我也很开心能作为微软社区MVP来参加这个活动.而我的演讲主题也和我的社区有关——Unity.C#以及跨平台开发. 这篇用来记录MSIg ...
- 原生js 通用事件绑定
/*原文地址:http://ejohn.org/blog/flexible-javascript-events/*/ http://blog.csdn.net/qi1271199790/article ...