设为首页 加入收藏

TOP

Java 虚拟机 11 :运行期优化(一)
2018-04-03 09:08:28 】 浏览:258
Tags:Java 虚拟 运行 优化

前言

http://www.cnblogs.com/xrq730/p/4839245.html,HotSpot采用的是解释器+编译器并存的架构,之前的这篇文章里面已经讲过了,本文只是把即时编译器这块再讲得具体一点而已。当然,其实本文的内容也没多大意义,90%都是概念上的东西,对于实际开发、实际解决项目里面的疑难问题并没有什么太大的帮助,只要看过就好了。

编译对象与触发条件

之前讲过,Sun使用的虚拟机之所以被叫做”HotSpot”,就是因为运行过程中会检测热点代码,那么运行过程中,会被即时编译器编译的”热点代码”有两类,即:

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

前者很好理解,一个方法被调用得多了,方法体内代码执行的次数自然就多,他成为”热点代码”也是理所当然。而后者则是为了解决一个方法只被调用过一次或者少量的几次,但是方法体内部存在循环次数较多的循环体问题,这样循环体的代码也被重复执行多次,因此这些代码也应该认为是”热点代码”。

那上面的问题描述中,所谓”多次”都不是一个具体、严谨的用语,那么多少次才算”多次”?还有,虚拟机如何统计一个方法或一段代码被执行过多少次呢?

判断一段代码是不是热点代码,是不是需要触发即时编译,这样的行为称为”热点探测“,其实热点探测并不一定要知道方法具体被调用了多少次,目前主要的热点探测判定方式有两种:

  • 基于采样的热点探测
  • 基于计数器的热点探测

HotSpot虚拟机中使用的是第二种基于计数器的热点探测方法,它为每个方法准备了两类计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译,分别看一下:

1、方法调用计数器

顾名思义,这个计数器就是用于统计方法被调用的次数,它的默认阈值在Client模式下是1500次,在Server模式下是10000次。这个阈值可以通过参数-XX:CompileThreshold来人为设定。当一个方法被调用时,会检查方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器和回边计数器值之和是否超过方法调用计数器的阈值。如果已经超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。

如果这个参数不做任何设置,那么方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会少一半,这个过程称为方法的调用计数器热度的衰减,而这段时间就称为此方法统计的半衰周期。进行热度衰减的动作实在虚拟机进行垃圾回收时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。

那如果参数不设置的话,执行引擎并不会同步等待编译请求完成,而是直接进入解释器按照解释方法执行字节码,直到提交的请求被编译器编译完成。当编译工作完成之后,这个方法的调用入口地址就会被系统自动改写成新的,下一次调用该方法时就会使用已编译的版本。

2、回边计数器

它的作用是统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为”回边”。显然,建立回边技术其统计的目的就是为了触发OSR编译。关于回边计数器的阈值,虽然HotSpot也提供了一个类似于方法调用计数器阈值-XX:CompileThreshold的参数-XX:BackEdgeThreshold供用户设置,但是当前虚拟机实际上并未使用此参数,因此我们需要设置另外一个参数-XX:OnStackReplacePercentage来间接调整回边计数器的阈值,其计算公式如下:

(1)Client模式

方法调用计数器阈值 × OSR比率 / 1000,其中OSR比率默认值933,如果都取默认值,Client模式下回边计数器的阈值应该是13995

(2)Server模式

方法调用计数器阈值 × (OSR比率 – 解释器监控比率) / 100,其中OSR比率默认140,解释器监控比率默认33,如果都取默认值,Server模式下回边计数器阈值应该是10700

当解释器遇到一条回边指令时,会先查找将要执行的代码片段中是否有已经编译好的版本,如果有,它将会优先执行已编译好的代码,否则就把回边计时器的值加1,然后判断方法调用计数器与回边计数器值之和是否已经超过回边计数器的阈值。当超过阈值之后,将会提交一个OSR编译请求,并且把回边计数器的值降低一些,以便继续在解释器中执行循环,等待编译器输出编译结果。

与方法计数器不同,回边计数器没有热度衰减的过程,因此这个计数器统计的就是该方法循环执行的绝对次数。当计数器溢出的时候,它还会把方法计数器的值也调整到溢出状态,这样下次再进入该方法的时候就会执行标准编译过程。

编译过程

很简单过一下这块编译过程的内容,因为这主要是编译原理和代码优化中的内容。

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

对于Client Compiler(C1编译器)来说,它是一个简单快速的三段式编译,主要关注点在于局部性的优化,而放弃了许多耗时间长的全局优化手段。

对于Sever Compiler(C2编译器)来说,它则是专门面向服务端的典型应用并为服务端的性能配置特别调整过的编译器,也是一个充分优化过的高级编译器,几乎能达到GNU C++编译器使用-O2参数时的优化强度,它会执行所有经典的优化动作,如无用代码消除、循环展开、常量传播、基本块重排序等,还会实施一些与Java语言特性密切相关的优化技术,如范围检查消除、空值检查消除等,另外,还有可能根据解释器或Client Compiler提供的性能监控信息,进行一些不稳定的激进优化,如守护内联、分支频率预测等,下一部分将讲解上述的一部分优化手段。

Server Compiler从即时编译的标准来看,无疑是比较缓慢的,但它的编译速度依然远远超过传统的静态优化编译器,而且它相对于Client Compiler编译输出的代码质量有所提高,可以减少本地代码的执行时间,从而抵消了额外的编译时间开销,所以也有很多非服务端的应用选择使用Server模式的虚拟机运行。

优化技术

在Sun官方的Wiki上,HotSpot虚拟机设计团队列出了一个相对比较全面、在即时编译器中采用的优化技术列表,其中有不少经典编译器的优化手段,也有许多针对Java语言(准确

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Java 虚拟机10:类加载器 下一篇Java 虚拟机 13:互斥同步、锁优..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目