详解JVM的即时编译

JVM之所以拥有强大的生态,是因为它是跨语言性的,JVM只识别字节码文件,不论是什么语言编写的代码,只要经过编译后能生成.class的字节码文件,JVM都可以进行解析。当然这些字节码文件都要符合JVM对于Class文件的格式定义,不能随便一个文件将后缀改成.classs就能行的。

像Groovy、Kotlin、Scala等语言,它们编译后生成的都是字节码文件,所以它们可以在JVM上运行。

字节码文件的内容除了一些常量池的一些信息外,其他基本都是每个方法中语句对应的字节码指令,像下面这样包含了部分main()方法的字节码指令:

这篇文章主要介绍JVM是如何去执行这些字节码指令

一、解释器与JIT

程序运行的时候,需要将字节码文件加载进内存,然后当调用类的某个方法时,就去方法区找到对应方法经过编译之前的解码指令,我们都知道对于机器来说,它只认识机器码(也就是0和1的组合),而字节码是JVM定义的一种指令集,那么想要让机器认识这些字节码,就需要JVM的执行引擎能够把字节码转换成机器码。

而将字节码转换为机器码这一步就产生了不同的方式,主要包括解释器和即时编译器(Just In Time Compiler,JIT)。

解释器比较好理解,就是当JVM在执行字节码指令的时候,逐行将字节码指令转换成机器码指令,大概就是下面代码这样:

public void compile(String byteCodeInstruct){
    switch (byteCodeInstruct){
        case  "iload":
            //010101010101010 将字节码指令翻译成机器码指令
            break;
        case "bipush":
            //111001010110110
            break;
        default:break;
    }
}

方法的字节码一般这些执行会按照顺序解释执行,但对于那些被频繁调用的代码,比如调用次数很多或者在for/while循环里面的代码,如果仍然按照解释执行,效率是非常慢的。这也是Java被吐槽慢的原因。

上面说到的被频繁调用的这些代码称为热点代码,所以为了提供热点代码的运行效率,在运行时,JVM将会把这些代码直接编译成与当前平台相关的机器码(不同平台window/linux同一字节码对应的机器码是不同的),然后再进行各种层次的优化,这就是即时编译器(Just In Time Compiler),简称JIT编译器要干的事。

二、JIT编译器

2.1 JIT编译器分类

以HostSpot虚拟机为例,目前已有的JIT编译器主要有C1、C2和Graal。

在JDK1.8中,内置了两个JIT编译器,分别为C1编译器和C2编译器,而从JDK10开始,就抛弃了C2编译器,使用全新的Graal编译器来替换。

C1编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,比较适用于执行时间较短或对启动性能有要求的程序,比如GUI应用对界面启动速度就有一定要求,C1同时也被称为Client Compiler,它几乎不会对代码进行优化。

C2编译器是为长期运行的服务器端应用程序做性能优化的编译器,适用于执行时间较长或对峰值性能要求比较高的程序,根据它的这种特性,该即时编译器也成为Server Compiler。

但随着JVM发展了这么多年,C2已经变成了一个非常庞大且难以维护的工程,于是才有了后面的替代产品Graal编译器。关于Graal编译器在下一篇文章会重点介绍,Graal编译器将成为未来Java在云原生领域的重要基石。

2.2 分层编译

在 Java7之前,需要根据程序的特性来选择对应的 JIT,虚拟机默认采用解释器和其中一个编译器配合工作。

Java7及以后引入了分层编译,这种方式综合了 C1 的启动性能优势和 C2 的峰值性能优势,当然我们也可以通过参数强制指定虚拟机的即时编译模式。

以JDK8为例,默认开启分层编译。

可以通过java -version命令查看当前系统使用的编译模式(默认分层编译)
在这里插入图片描述

使用-Xint参数可以强制虚拟机运行于只有解释器的编译模式:
在这里插入图片描述

使用-Xcomp强制虚拟机运行于只有 JIT 的编译模式下
在这里插入图片描述

2.3 热点代码

对于热点代码,经过多次编译后被认为是热点代码,那么再次编译后的机器码会被缓存起来,当下一次再使用时就不需要再次编译了,而对于执行次数比较少的代码,这种编译动作就显得很浪费。

JVM提供了参数-XX:ReservedCodeCacheSize来限制CodeCache的大小,也就是说,JIT编译后的代码会放在CodeCache中。

如果CodeCache的空间不足,JIT就无法继续编译,会从编译执行变成解释执行,性能也会跟着大幅下降。同时JIT编译器会一直尝试优化代码,从而造成CPU占用上升。

通过 java -XX:+PrintFlagsFinal –version可以查看JVM所有参数:
在这里插入图片描述

2.4 热点探测

在HostSpot虚拟机中,热点探测是JIT优化的前提条件,热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。

虚拟机为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)回边计数器(Back Edge Counter)

在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发 JIT 编译。

2.4.1 方法调用计数器

统计方法被调用的次数,方法调用计数器的默认阈值在客户端模式下是1500 次,在服务端模式下是10000 次(一般用的都是服务端,通过java –version查询),可通过 -XX: CompileThreshold 来设定
在这里插入图片描述

通过java -XX:+PrintFlagsFinal –version查看默认的参数值:
在这里插入图片描述

2.4.2 回边计数器

统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge),该值用于计算是否触发 C1 编译的阈值,在不开启分层编译的情况下,在服务端模式下是10700。

回边计数器阈值 =方法调用计数器阈值(CompileThreshold)×(OSR比率(OnStackReplacePercentage)-解释器监控比率(InterpreterProfilePercentage)/100
在这里插入图片描述
在这里插入图片描述

如果这些参数都使用默认值,回边计数器阈值默认就是10000*(140-33)/100 = 10700。

三、JIT编译优化

JIT编译优化用到了一些经典的编译优化技术来实现代码的优化,即通过一些例行检查优化,可以智能地编译出运行时的最优性能代码。

3.1 方法内敛

方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。

比如下面的例子:

public void test(int a,int b){
    int c = 10;
    int d = compute(a,b) + c;
}

public int compute(int a,int b){
    return a + b;
}

优化之后就变成这样:

public void test(int a,int b){
    int c = 10;
    int d = a + b + c;
}

JVM 会自动识别热点方法,并对它们使用方法内联进行优化,需要注意的是只有当方法调用次数大于热点方法的阈值才会进行方法内敛。

可以通过 -XX:CompileThreshold 来设置热点方法的阈值

但热点方法也不是一定会被 JVM 做内联优化,如果这个方法体太大了,JVM 将不执行内联操作。

而方法体的大小阈值,也可以通过参数设置来优化:

对于经常执行的方法,默认情况下,方法体大小小于 325 字节的都会进行内联,可以通过参数-XX:FreqInlineSize=N 来设置大小值
在这里插入图片描述

而对于不太经常执行的方法,默认情况下,方法大小小于 35 字节才会进行内联,也可以通过参数-XX:MaxInlineSize=N来重置大小值
在这里插入图片描述

3.2 其他优化手段

除了方法内敛外,JIT还实现了锁消除、标量替换以及逃逸分析等优化手段,这些内容在该专题的其他文章中都有介绍过,锁消除可以参考《深入理解Synchronized》,标量替换可以参考《JVM内存分配》中介绍栈上分配的内容,逃逸分析在这两篇文章中也都有介绍过,这里就不再赘述。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值