更新时间:2021-06-02 15:18:25 来源:极悦 浏览761次
学习JVM相关的知识,必然绕不开即时编译器,因为它太重要了。了解了它的基本原理及优化手段,在编程过程中可以让我们有种打开任督二脉的感觉。比如,很多朋友在面试当中还会遇到这样的问题:Java是基于编译执行还是基于解释执行?当你了解了Java的即时编译器,不仅能够轻松回答上述问题,还能如数家珍的讲出JVM在即时编译器上采用的优化技术,而且在实践过程中更深刻的理解代码背后的原理。本文便带大家全面的了解Java即时编译器。
在部分的商用虚拟机中,比如HotSpot中,Java程序先通过解释器(Interceptor)进行解释执行。这也是为什么称Java是基于解释执行的原因。但当虚拟机发现某块代码或方法运行的特别频繁,便会将其标记为“热点代码”(Hot Spot Code)。
针对热点代码,虚拟机会采用各种措施来提升其执行效率,因为执行比较频繁,如果能够提升其执行效率,性价比还是比较高的。为此,在运行时,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各层次的深度优化。而这些优化操作便是通过编译器来完成的,也称作即使编译器(Just In Time Compiler,简称JIT编译器)。
因此,准确的来说,像HotSpot等虚拟机,Java是基于解释执行和编译执行的。下面用一张图来解释该过程:
首先,我们需要知道并不是所有的Java虚拟机都采用解释器与编译器并存的架构,但许多主流的商用虚拟机(如HotSpot),都同时包含解释器和编译器。
既然即时编译器进行了各层次的优化,那么为什么Java还使用解释器来“拖累”程序的性能呢?这是因为,解释器与编译器两者各有优势:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,反之可以使用编译执行来提升效率。此外,如果编译后出现“罕见陷阱”,可以通过逆优化退回到解释执行。
Java虚拟机运行时,解释器和即时编译器能够相互协作,取长补短。无论采用解释器进行解释执行,还是采用即使编译器进行编译执行,最终字节码都需要被转换为对应平台的本地机器码指令。某些服务并不看重启动时间,而某些服务却非常看重,这就需要采用解释器与即时编译器并存来换取一个平衡点。
我们可以从解释器和编译器的编译时间开销和编译空间开销两方面进行对比。首先,看编译的时间开销。
我们所说的JIT比解释器快,仅限于对“热点代码”编译之后的代码执行起来要比解释器解释执行的快。通过上图可以看出,如果是只是单次执行的代码,JIT编译比解释器要多出一步“执行编译”,因此,只执行一次时,JIT是要比解释器慢的。只执行一次的代码通常包括只被调用一次的代码(比如构造器)、没有循环的代码等,此时使用JIT显然得不偿失。
其次,再来看看编译空间方面的开销。对一般的Java方法而言,编译后代码的大小相对于字节码,膨胀比达到10倍是很正常的。只有对执行频繁的代码才值得编译,如果把所有代码都编译则会显著增加代码所占空间,导致“代码爆炸”。这就是为什么有些JVM不会单一使用JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。
HotSpot虚拟机为了使用不同的应用场景,内置了两个即时编译器:Client Complier和Server Complier,简称为C1、C2编译器,分别用在客户端和服务端。Client Complier可获取更高的编译速度,Server Complier可获取更好的编译质量。
JVM Server模式与client模式最主要的差别在于:-server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client模式时,使用的是一个代号为C1的轻量级编译器,而-server模式启动的虚拟机采用相对重量级代号为C2的编译器。C2比C1编译器编译的相对彻底,服务起来之后,性能更高。
默认情况下,使用C1还是C2编译器,要取决于虚拟机运行的模式。HotSpot虚拟机会根据自身版本与宿主机器的硬件性能自动选择运行模式,用户也可以使用“-client”或“-server”参数去强制指定虚拟机运行在Client模式或Server模式。
目前主流的HotSpot虚拟机中默认是采用解释器与其中一个编译器配合的方式工作,这种配合称作混合模式(Mixed Mode)。用户可以使用参数-Xint强制虚拟机运行于“解释模式”(Interpreted Mode),这时候编译器完全不介入工作。使用-Xcomp强制虚拟机运行于“编译模式”(Compiled Mode),这时将优先采用编译方式执行,但是解释器仍然要在编译无法进行的情况下接入执行过程。通过虚拟机java-version命令可以查看当前默认的运行模式。
在上述示例中我们不仅能够看到采用的模式为mixed mode,还能看到出采用的是Server模式。
上面解释了JIT编译器的基本功能,那么它是如何判断热点代码的呢?判断一段代码是不是热点代码的行为,也叫热点探测(Hot Spot Detection),通常有两种方法:基于采样的热点探测和基于计数器的热点探测(HotSpot使用此方式)。
基于采样的热点探测(Sample Based Hot Spot Detection):Java虚拟机会周期的对各个线程栈顶进行检查,如果某些方法经常出现在栈顶,会被定义为“热点方法”。实现简单、高效,很容易获取方法调用关系。但很难确认方法的reduce,容易受到线程阻塞或其他外因扰乱。
基于计数器的热点探测(Counter Based Hot Spot Detection):为每个方法(甚至是代码块)建立计数器,执行次数超过阈值就认为是“热点方法”。统计结果精确严谨,但实现麻烦,不能直接获取方法的调用关系。
HotSpot虚拟机默认采用基于计数器的热点探测,有两种计数器:方法调用计数器和回边计数器。当计数器数值大于默认阈值或指定阈值时,方法或代码块会被编译成本地代码。
方法调用计数器,记录方法调用的次数。Client模式默认阈值是1500次,在Server模式下是10000次,可以通过-XX:CompileThreadhold来设定。如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内的方法被调用的次数。当超过一定的时间限度,但调用次数仍然未达到阈值,那么该方法的调用计数器就会被减半,称为方法调用计数器热度的衰减(Counter Decay),这段时间称为此方法的统计半衰周期(Counter Half Life Time)。进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。JIT编译交互图如下:
回边计数器,统计一个方法中循环体代码执行的次数。在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge),建立回边计数器统的目的是为了触发OSR编译。计数器的阈值,HotSpot提供了-XX:BackEdgeThreshold来进行设置,但当前的虚拟机实际上使用了-XX:OnStackReplacePercentage来间接调整阈值,计算公式如下:
与方法计数器不同,回边计数器没有计数热度衰减的过程,因此统计的就是该方法循环执行的绝对次数。当计数器溢出时,它还会把方法计数器的值也调整到溢出状态,这样下次再进入该方法的时候就会执行标准编译过程。
以上就是极悦小编介绍的"Java编译器有哪些用法",希望对大家有帮助,如有疑问,请在线咨询,有专业老师随时为您服务。
0基础 0学费 15天面授
Java就业班有基础 直达就业
业余时间 高薪转行
Java在职加薪班工作1~3年,加薪神器
工作3~5年,晋升架构
提交申请后,顾问老师会电话与您沟通安排学习