简述 即时编译器(JIT)

1 篇文章 0 订阅

前情提要,Java 既有解释执行,也有编译执行,为了解决解释器的性能瓶颈问题,优化 Java 的性能,引入了即时编译器,大幅度的提高运行效率。那么,即时编译器又是何方神圣呢,接下来让我们一起去揭开它神秘的面纱!

什么是即时编译

Java 代码的执行过程中,首先进行前端编译,通过编译器将 .java文件 编译成字节码,即 .class文件。
然后,进行后端编译,将字节码编译成机器码,在此过程中,有一个编译器将热点代码编译成本地平台相关的机器码,并进行优化。

这个编译器就是即时编译器,也称JIT编译器。

JVM 有三种执行方式:解释执行、编译执行、混合模式(默认)

使用命令 java -version 可以查看 JVM 的执行方式

图1
图1

◉ mixed mode:表示混合模式

◉ Server VM:表示 JVM 的类型是 Server Compiler

有哪些编译器

JVM 集成的编译器有两种模式:

◉ Client Compiler

◉ Server Compiler

Client Compiler

注重启动速度和局部优化,HotSpot VM 使用的是 Client Compiler C1编译器,简称 C1编译器。

Server Compiler

注重全局优化,运行过程中性能更好,由于进行更多的全局分析,所以启动速度会变慢。Hotspot VM 使用有两种:C2编译器(默认)和 Graal编译器(暂时不讨论)。

分层编译

C1、C2 都有各自的优缺点,为了综合两者的优势,在编译速度和执行效率之间取得平衡,JVM 引入了一种策略:分层编译。

◉ 0级(解释执行):采用解释执行,不开启性能监控功能(profiling)。

◉ 1级(简单的 C1 编译):采用 C1编译器,进行简单可靠的优化,不开启性能监控功能。

◉ 2级(有限的 C1 编译):采用 C1编译器,进行更多的优化编译,开启性能监控功能,统计方法计数器的值,以及回边计数器的值等。

◉ 3级(完全 C1 编译):完全使用 C1编译器 的所有功能,完全开启性能监控功能。

◉ 4级(C2 编译):完全使用 C2编译器 进行编译,进行完全的优化。

注意:分层编译只能在 Server Compiler 模式下启用。

其中,这几个层次的执行效率是这样的:4 > 1 > 2 > 3 > 0。

C2 代码的执行效率比 C1 的高出30%以上。

1 > 2 > 3 的主要原因是性能监控功能开启越多,其性能开销也越大。

列举几种常见的编译路径,如下图所示:

图2
图2

什么是热点代码

JVM 设置一个阈值,当方法或者代码块的在一定时间内的调用次数超过这个阈值,就会被编译,存入 CodeCache。

再次执行这段代码时,直接从 CodeCache 中读取机器码执行,使执行效率大幅提升。

Java 代码的执行过程如下图所示:

图3
图3

标记热点代码的行为,叫热点探测,通常有两种方法:

◉ 基于计数器的热点探测

◉ 基于采样的热点探测

◉ 基于踪迹的热点探测

目前 HotSpot VM 所采用的热点探测方式是基于计数器的热点探测。

基于计数器的热点探测

JVM 为每个方法(或者代码块)建立计数器,执行次数超过阈值就会标记为热点代码。

计数器分为两类:方法调用计数器、回边计数器。

在 Client 模式的前提下:

(因为 Server 模式下比较复杂,尤其是启用分层模式的情况)

方法调用计数器

统计方法调用的次数,通过参数 -XX:CompileThreshold 设定阈值。

注:分层编译的情况下,参数将会失效

默认情况,方法调用计数器统计的并不是方法调用的次数,而是方法调用的频率,即一定时间内方法调用的次数。

如果,一定的时间内,如果两个计数器之和没达到阈值,那么该方法调用计数器的值将会被减半,这个行为称为方法调用计数器热度的衰减,这段时间称为此方法的统计半衰周期。这个行为是在 JVM 进行垃圾回收时进行的,通过参数  -XX:CounterHalfLifeTime 设置半衰周期,单位秒。

除此之外,通过参数 -XX:-UseCounterDecay 可以关闭热度的衰减,那么方法调用计数器统计的就是方法调用的实际次数。

回边计数器

统计循环体代码执行的次数,通常使用 -XX:OnStackReplacePercentage 间接设定阈值。

回边计数器没有热度的衰减,设定的阈值就是方法循环执行的实际次数。

-XX:OnStackReplacePercentage,又称 ORS比率,计算公式如下:

◉ Server Compiler

方法调用计数器阈值 × OSR比率 / 1000,其中 OSR比默认值933。

默认情况下,Client 模式下回边计数器的阈值应该是13995。

◉ Server Compiler

方法调用计数器阈值 × (OSR比率 - 解释器监控比率) / 100,其中 OSR比率 默认140,解释器监控比率默认33。

默认情况下,Server 模式下回边计数器阈值应该是10700。

再说几句

即时编译器的作用能够极大的提高 Java 运行过程中的效率,以上内容只是简单的阐述了即时编译器的基础知识。背后还有许多值得深入探讨的问题,例如编译优化,后面将会逐一呈现,尽情期待!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值