【Java 虚拟机探索之路系列】:JIT编译器

作者:郭嘉

邮箱:[email protected]

博客:http://blog.csdn.net/allenwells

github:https://github.com/AllenWell

为什么会Java虚拟机会同时存在解释器和编译器呢?

这是为了兼顾启动效率和执行效率两个方面。Java程序最初是通过解释器进行解释执行的,当虚拟机返现某个方法或代码块的运行特别频繁时,就会把这段代码标记为热点代码,为了提供热点代码的执行效率,在运行时,虚拟机就会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。

当编译器做的激进优化不成立,不如加载了新类后类型继承结构出现变化,出现了罕见陷阱时可以进行逆优化退回到解释状态继续执行。

以上描述的两种配合关系如下图所示:

一 编译模式

HotSpot JVM内置了两个编译器,分别是Client Complier和Server Complier,虚拟机默认是Client模式,我们也可以通过

  • -client:强制虚拟机运行Client模式
  • -server:强制虚拟机运行Server模式

而无论是Client模式还是Server模式,虚拟机都会运行在解释器和编译器配合使用的混合模式下,可以通过

  • -Xint:强制虚拟机运行于解释模式
  • -Xcomp:强制虚拟机运行于编译模式

以上描述的运行模式如下图所示:

二 分层编译

为什么会存在分层编译?

这是因为编译器编译本机代码需要占用程序运行时间,要编译出优化程度更高的代码锁花费的时间可能更长,而且想要编译出优化程度更高的代码,解释器可能还要替编译器收集性能监控信息,这对解释执行的速度也有影响。为了在程序启动响应速度和运行效率之间寻找平衡点,因此采用分层编译的策略。

分层策略如下所示:

  • 第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译。
  • 第1层:即C1编译,将字节码编译为本地代码,进行简单和可靠的优化,如有必要将加入性能监控的逻辑。
  • 第2层:即C2编译,将字节码编译为本地代码,同时启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

三 编译对象

编译对象即为会被编译优化的热点代码,有以下两类:

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

四 触发条件

上面描述中使用多次这个概念,那么什么算多次呢?

这就牵扯到触发条件这个概念,判断一段代码是否是热点代码,是否需要触发即时编译,这种行为成为热点探测(Spot Dectection)。

热点探测有两种手段:

4.1 基于采样的热点探测(Sample Based Hot Spot Dectection)

虚拟机会周期性的检查各个线程的栈顶,如果发现某些方法经常性的出现在栈顶,那么这个方法就是热点方法。

4.2 基于计数器的热点探测(Counter Based Hot Spot Dectection)

虚拟机会为每个方法或代码块建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为他是热点方法。

HotSpot JVM使用第二种方法基于计数器的热点探测方法,它为每个方法准备了两类计数器:

4.2.1 方法调用计数器

这个阈值在Client模式下是1500次,在Server模式下是10000此,这个阈值可以通过参数-XX:CompileThreadhold来人为设定。

如果不做任何设置,方法调用次数统计的并不是方法被调用的绝对次数,而是相对的执行频率,即一段时间内方法被调用的次数,当超过一定时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器会被减少一半,这个过程被称为方法调用计数器的热度衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。同样也可以使用参数-XX:-UseCounterDecay来关闭热度衰减。

方法调用计数器触发即时编译的整个流程如下图所示:

4.2.2 回边计数器

什么是回边?

在字节码遇到控制流向后跳转的指令称为回边(Back Edge)。

回边计数器是用来统计一个方法中循环体代码执行的次数,回边计数器的阈值可以通过参数-XX:OnStackReplacePercentage来调整。

  • 虚虚拟机运行在Client模式下,回边计数器阂值计算公式为:
方法调用计数器闭值( CompileThreshold) xOSR比率(OnStackReplacePercentage) / 100

其中OnSlackReplacePercentage默认值为933,如果都取默认值.那Client模式虚拟机的回边计数器的阂值为13995.

  • 虚拟机运行在Servo模式下,回边计数器阂值的itm公式为:
方法调用计数器阂值(CompileThmshold) x (OSR比率(OnStackReplacePercentage) - 解释器监控比率(InterpreterProffePercentage) / 100

其中OnStackReplacePementage默认值为140. InterpreterPmfilePercenmgc默认值为33.

如果都取默认值,BF Server模式虚拟机回边计数器的阑值为10700。

回边计数器触发即时编译的流程如下图所示:

回边计数器与方法调用计数器不同的是,回边计数器没有热度衰减,因此这个计数器统计的就是循环执行的绝对次数。

五 编译流程

在默认设置下,无论是方法调用产生的即时编译请求,还是OSR编译请求,虚拟机在代码编译器还未完成之前,都仍然按照解释方式继续进行,而编译动作则在后台的编译线程中继续进行。也可以使用-XX:-BackgroundCompilation来禁止后台编译,则此时一旦遇到JIT编译,执行线程向虚拟机提交请求后会一直等待,直到编译完成后再开始执行编译器输出的本地代码。

那么在后台编译过程中,编译器做了什么事呢?

Server Compiler和Client Compiler的后台编译过程是不一样的,我们来分别看一下。

5.1 Client Compiler编译流程

  1. 第一阶段:一个平台独立的前端将字节码构造成一种高级中间码表示(High Level Infermediate Representaion),HIR使用静态单分配的形式来表示代码值,这可以使得一些的构造过程之中和之后进行的优化动作更容易实现,在此之前编译器会在字节码上完成一部分基础优化,如方法内联、常量传播等。
  2. 第二阶段:一个平台相关的后端从HIR中产生低级中间代码表示(Low Level Intermediate Representation),而在此之前会在HIR上完成另一些优化,如空值检查消除、范围检查消除等,以便让HIR达到更高效的代码表示形式。
  3. 第三阶段:在平台相关的后端使用线性扫描算法(Linear Scan Register Allocation)在LIR上分配寄存器,并在LIR上做窥孔优化(Peephole)优化,然后产生机器码。

整个过程如下图所示:

5.1 Server Compiler编译流程

Server Compiler是专门面向服务端的典型应用并为服务器的性能配置特别调整过的编译器,它会执行所有经典的优化动作,如下所示:

  • 无用代码消除
  • 循环展开
  • 循环表达式外提
  • 消除公共子表达式
  • 常量传播
  • 基本块重排序
  • 范围检查消除
  • 空值检查消除
  • 守护内联
  • 分支频率预测

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-25 20:19:45

【Java 虚拟机探索之路系列】:JIT编译器的相关文章

【Java】实战Java虚拟机之五“开启JIT编译”

今天开始实战Java虚拟机之五"开启JIT编译" 总计有5个系列 实战Java虚拟机之一"堆溢出处理" 实战Java虚拟机之二"虚拟机的工作模式" 实战Java虚拟机之三"G1的新生代GC" 实战Java虚拟机之四"禁用System.gc()" 实战Java虚拟机之五"开启JIT编译" Java虚拟机有3种执行方式,分别是解释执行.混合模式和编译执行,默认情况下处于混合模式中.使用命令行

【Java 安全技术探索之路系列:J2SE安全架构】之六:安全管理工具

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell [Java 安全技术探索之路系列:J2SE安全架构]章节列表 [Java安全技术探索之路系列:J2SE安全架构]之一:J2SE安全架构开篇 [Java 安全技术探索之路系列:J2SE安全架构]之五:类加载器 [Java 安全技术探索之路系列:J2SE安全架构]之六:安全管理工具 作为J2SE复合包的一部

Java虚拟机即时编译器技术JIT

Java虚拟机中有许多附加技术用以提升速度,尤其是与加载器操作相关的,被称为"即时"(Just-In-Time,JIT)编译器的技术.这种技术可以把程序全部或部分翻译成本地机器码(这本来是JVM的工作),程序运行速度因此得以提升.当需要装载某个类时,编译器会先找到其.class文件,然后将该类的字节码装入内存.此时,有两种方案可供选择: (1)一种就是让即时编译器编译所有代码.但这种做法有两个缺陷:这种加载动作散落在整个程序生命周期内,累加起来要花更多时间;并且会增加可执行代码的长度(

【Java 安全技术探索之路系列:J2SE安全架构】之二:安全管理器

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell 一 安全管理器的功能 安全管理器是一个允许程序实现安全策略的类,它会在运行阶段检查需要保护的资源的访问权限及其它规定的操作权限,保护系统免受恶意操作攻击,以达到系统的安全策略. 安全管理器负责检查的操作主要包括以下几个: 创建一个新的类加载器 退出虚拟机 使用反射访问另一个类的成员 访问本地连接 打开s

【Java Swing探索之路系列】之二:Java Swing布局面板组件

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell 面板指的是容器,即中间容器类,可以将基本组件放置在其中,组成丰富多彩的用户界面. 一 JPanel 在JPanel的使用中,有一个双缓冲的概念,这个技术旨在改进频繁变化的组件显示效果.可以在JPanel初始化的时候进行定义如下所示: 举例 展示如何使用JPanel中间容器类. import javax.

【Java安全技术探索之路系列:Java可扩展安全架构】之四:JCA(三):JCA编程模型

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell 一 消息摘要 使用MD5计算消息摘要 try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] testdata = { 1, 2, 3, 4, 5 }; md5.update(testdata); byte[] my

【Java Swing探索之路系列】之一:Java Swing开篇

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell Java Swing是一个用于开发Java图形界面应用程序的开发工具包,它一抽象窗口工具包(Abstract Window Toolkit)为基础,是跨平台应用程序可以使用任何可插拔的外观风格. 一 Swing特性 多样化的Ul组件:包括从技钮.分隔窗格到表格的所有组件. 可插式外观样式:允许任何使用S

【Java Swing探索之路系列】之三:Java Swing布局管理器组件

作者:郭嘉 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells github:https://github.com/AllenWell 一 BorderLayout BorderLayout是一种简单的布局策略,可以将其看作一个组件.它把容器分为东.南.西.北.中5个区域,每个组件将占据某个区域.而 这5个区域分别被命名为NORTH, WEST, EAST, CENTER, SOUTH,它们都被定义为静态 常量.静态常量可以直接引用,如

【深入Java虚拟机】之七:Javac编译与JIT编译

转载请注明出处:http://blog.csdn.net/ns_code/article/details/18009455 编译过程 不论是物理机还是虚拟机,大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行: 其中绿色的模块可以选择性实现.很容易看出,上图中间的那条分支是解释执行的过程(即一条字节码一条字节码地解释执行,如JavaScript),而下面的那条分支就是传统编译原理中从源代码到目标机器代码的生成过程. 如今,基于物理机