JVM对代码执行的优化可分为运行时(runtime)优化和即时编译器(JIT)优化。
运行时优化主要是解释执行和动态编译通用的一些机制。比如说锁机制(如偏斜锁)、内存分配机制(如TLAB)等。除此之外,还有一些专门用于优化解释执行效率的,比如说模版解释器、内联缓存(inline cache,用于优化虚方法调用的动态绑定)

什么是 JIT?

在部分的商用虚拟机(Sun HotSpot、IBM J9)中,Java程序最初是通过解释器(Interpreter)进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”(Hot Spot Code)。为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,本文中简称JIT编译器)。

热点代码的概念查看下文

什么是编译和解释?

编译器:把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样运行时计算机可以直接以机器语言来运行此程序,速度很快
解释器:只在执行程序时,才一条一条把字节码解释成机器语言给计算机来执行

Java需要将字节码逐条翻译成对应的机器指令并且执行,这就是传统的JVM的解释器的功能,正是由于解释器逐条翻译并执行这个过程的效率低,引入了JIT即时编译技术。 必须指出的是,不管是解释执行,还是编译执行,最终执行的代码单元都是可直接在真实机器上运行的机器码

其实这种思想很常见,就像编译器是读库操作,解释器是将热点机器码缓存起来。正如某位计算机大佬曾经说过的,没有任何一个计算机问题不能通过加一层来解决的~哈哈

即时编译器的分类

在 HotSpot 中,解释器和 JIT 即时编译器是同时存在的,他们是 JVM 的两个组件。对于不同类型的应用程序,用户可以根据自身的特点和需求,灵活选择是基于解释器运行还是基于 JIT 编译器运行。HotSpot 为用户提供了几种运行模式供选择,可通过参数设定,分别为:解释模式、编译模式、混合模式,HotSpot 默认是混合模式,需要注意的是编译模式并不是完全通过 JIT 进行编译,只是优先采用编译方式执行程序,但是解释器仍然要在编译无法进行的情况下介入执行过程。

解释器与编译器并存的优势

解释器与编译器两者各有优势

  • 解释器:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。

  • 编译器:在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。

两者的协作:在程序运行环境中内存资源限制较大时,可以使用解释执行节约内存,反之可以使用编译执行来提升效率。当通过编译器优化时,发现并没有起到优化作用,可以通过逆优化退回到解释状态继续执行。

即时编译器与 Java 虚拟机的关系

即时编译器并不是虚拟机必需的部分,Java虚拟机规范并没有规定Java虚拟机内必须要有即时编译器的存在,更没有限定或指导即时编译器应该如何去实现。
但是,即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一。它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。

热点代码

热点代码的分类

  • 被多次调用的方法
    方法体内的代码会被标定为“热点代码”

  • 被多次执行的循环体
    一个方法只被调用过一次或少量的几次,但是方法体内部存在循环次数较多的循环体,这样循环体的代码也被重复执行多次,因此这些代码也应该认为是“热点代码”。

上面提到的多次是一个不具体的词语,那到底是多少次才能成为热点代码呢?

如何检测热点代码

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

  • 基于采样的热点探测:采用这种方法的虚拟机会周期性地检查各个线程的栈顶如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”

优点:实现简单高效,容易获取方法调用关系(将调用堆栈展开即可)
缺点:不精确,容易因为因为受到线程阻塞或别的外界因素的影响而扰乱热点探测

  • 基于计数器的热点探测:采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果次数超过一定的阈值就认为它是“热点方法”

优点:统计结果精确严谨
缺点:实现麻烦,需要为每个方法建立并维护计数器,不能直接获取到方法的调用关系
HotSpot使用第二种,基于计数器的热点探测方法。

热点次数计算

确定了检测热点代码的方式,如何计算具体的次数呢? 计数器的种类(两种共同协作)

  • 方法调用计数器:这个计数器用于统计方法被调用的次数。默认阈值在 Client 模式下是 1500 次,在 Server 模式下是 10000 次

  • 回边计数器:统计一个方法中循环体代码执行的次数

两个计数器的协作(这里讨论的是方法调用计数器的情况):当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本,如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器加1,然后判断方法调用计数器与回边计数器之和是否超过方法调用计数器的阈值。如果已经超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
当编译工作完成之后,这个方法的调用入口地址就会被系统自动改成新的,下一次调用该方法时就会使用已编译的版本。 如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time)。进行热度衰减的动作是在虚拟机进行垃圾收集时顺便进行的,可以使用虚拟机参数-XX:-UseCounterDecay来关闭热度衰减,让方法计数器统计方法调用的绝对次数,这样,只要系统运行时间足够长,绝大部分方法都会被编译成本地代码。另外,可以使用-XX:CounterHalfLifeTime参数设置半衰周期的时间,单位是秒。

简单的调优手段

1. 调整热点代码门限值

JIT的默认门限,server模式默认10000 次,client是1500次。门限大小也存在着调优的可能,可以使用-XX:CompileThreshold=N参数调整;与此同时,该参数还可以变相起到降低预热时间的作用。
JVM会周期性的对计数的数值进行衰减操作,所以并不会所有代码的调用计数器都能达到门限值,除了可以利用-XX:CompileThreshold=N适当调整大小,还有一个办法就是使用-XX:-UseCounterDecay关闭计数器衰减。

2. 调整Code Cache大小

JIT编译的代码是存储在Code Cache中的,需要注意的是 Code Cache 是存在大小限制的,而且不会动态调整。这意味着,如果Code Cache太小,可能只有一小部分代码可以被JIT 编译,其他的代码则没有选择,只能解释执行。所以,一个潜在的调优点就是调整其大小限制 -XX:ReservedCodeCacheSize=<SIZE>
当然,也可以调整其初始大小-XX:InitialCodeCacheSize=<SIZE>
注意,在相对较新版本的Java中,由于分层编译(Tiered-Compilation)的存在,Code Cache的空间需求大大增加,其本身默认大小也被提高了。

Code Cache是一块独立于 Java堆之外的内存区域。除了JIT编译的代码之外,Java所使用的本地方法代码(JNI)也会存在codeCache中。

从Java7开始,HotSpot虚拟机默认采用分层编译的方式:热点方法首先被C1编译器编译,然后 热点方法中的热点再进一步被C2编译(理解为二次编译,根据前面的运行计算出更优的编译优化)。为了不干扰程序的正常运行,JIT编译时放在额外的线程中执行的,HotSpot根据实际CPU的资源,以 1:2的比例分配给C1和C2线程数。

3. 调整编译器线程数,或者选择适当的编译器模式

JVM 的编译器线程数目与我们选择的模式有关,选择client模式默认只有一个编译线程,而server模式则默认是两个,如果是当前最普遍的分层编译模式,则会根据 CPU 内核数目计算 C1 和 C2 的数值,你可以通过-XX:CICompilerCount=N参数指定的编译线程数。
在强劲的多处理器环境中,增大编译线程数,可能更加充分的利用 CPU 资源,让预热等过程更加快速;但是,反之也可能导致编译线程争抢过多资源,尤其是当系统非常繁忙时。例如,系统部署了多个 Java 应用实例的时候,那么减小编译线程数目,则是可以考虑的。
生产实践中,也有人推荐在服务器上关闭分层编译,直接使用 server 编译器,虽然会导致稍慢的预热速度,但是可能在特定工作负载上会有微小的吞吐量提高。

有关JIT你需要知道的相关推荐

  1. 你应该知道的25个非常有用的CSS技巧

    在我们的前端CSS编码当中,经常要设置特殊的字体效果,边框圆角等等,还要考虑兼容性的问题, CSS网页布局,说难,其实很简单.说它容易,往往有很多问题困扰着新手,在中介绍了非常多的技巧,这些小技巧与知 ...

  2. 关于机器学习,你应该知道的3个热门专业术语

    https://www.toutiao.com/a6683842829510246923/ 2019-04-25 22:43:48 关于机器学习,你应该知道的3个热门专业术语 原创: 吴郦军.罗人千 ...

  3. 有哪些事情是你成为程序员之后才知道的?

    来源 | 三太子敖丙(ID:JavaAudition) 昨天我教练问我:"有哪些事情是你成为程序员之后才知道的."我就写下来了. 身穿一件微微起球的格子衫,背着工整的双肩包,头发乱 ...

  4. 你需要知道的requestAnimationFrame

    你需要知道的requestAnimationFrame 随着前端的发展,css已经能够实现非常多的动画特效,但是仍然存在css无法完成的动画任务(比如页面滚动),通常的解决方案都是使用js中的setI ...

  5. 《抓住听众心理——演讲者要知道的100件事》一20.人们学习的最优长度是20分钟...

    本节书摘来异步社区<抓住听众心理--演讲者要知道的100件事>一书中的第1章,第20节,作者: [美]Susan M. Weinschenk 译者: 杨妩霞 , 杨煜泳 责编: 赵轩,更多 ...

  6. git分支指的是_你一定知道的Git分支模型

    原标题:你一定知道的Git分支模型 写在前面 本文不是一篇Git入门指南,也不是 Git命令行使用技巧的讲解,而是谈谈作者在过往工作中使用的几种代码版本管理工具的一些体会,同时重点讲解一下Git的分支 ...

  7. 29 个你必须知道的 Linux 命令

    29 个你必须知道的 Linux 命令 2016-08-12    分类:操作系统.编程开发.首页精华暂无人评论     来源:dwqs 分享到:更多0 虽然Linux发行版支持各种各样的饿GUI(g ...

  8. SQL Server 2005:你应该知道的13件事情

    距离微软的SQL Server 2005正式版的推出,已经将近一年的时间.随着最近两份研究报告的出炉,SQL Server 2005又引起了业界的关注和评论--微软凭借SQL Server 2005取 ...

  9. linux 终端 画圆,16个圆桌面Linux用户必须要知道的Shell命令

    16个圆桌面Linux用户必须要知道的Shell命令 16个圆桌面Linux用户必须要知道的Shell命令 日期:2014-05-16 浏览次数:20305 次 16个桌面Linux用户必须要知道的S ...

最新文章

  1. nodejs 运行linux命令,node.js执行shell命令
  2. 学习笔记之Iframe
  3. 【Demo 0116】堆的使用
  4. sklearn朴素贝叶斯分类器_机器学习06——朴素贝叶斯
  5. 如何接触到最新的前端动态、最前沿的前端技术
  6. lua检测表中是否有某个值_Lua基础知识总结(入职面试题)
  7. 大数据学习笔记24:利用MR改造Zebra项目
  8. 【算法题目】数组中的逆序对
  9. 内容页嵌套母版页时,内容页如何调用css和javascript
  10. 软件设计之UML的几种关系
  11. 将excel转为图片
  12. matlab由滤波的系数得到传输函数 设计带通滤波器 design fdatool设计IIR带通滤波器
  13. Python模块selenium实例:电影分类排名数据生成json,sqlite和excel(openpyxl)文件(一)
  14. Web前端技术课后作业
  15. epoll反应堆及ET模式下的EPOLLOUT学习总结
  16. 微信小程序关注公众号
  17. 搭建MineCraft私服
  18. MATLAB编程之PTB:把实验结果和实验前录入信息整合
  19. 终极解决大黄峰黑屏加信号问题,觉得有用请顶我
  20. 超市小程序怎么做_分享超市微信小程序制作步骤

热门文章

  1. Weakly Guiding Fibers(弱导光纤)
  2. 游戏开发中的复杂度与银弹
  3. 程序员数学--卡特兰数(Catalan number)
  4. 浙大翁恺老师C语言教程自学笔录-计算机和编程语言
  5. 微信小程序 java音乐播放器系统python php
  6. 教你在M1芯片的imac一体机上安装PS2021 附教程和方法适用于所有Mac
  7. android 滑动导航栏颜色渐变,Android App页面滑动标题栏颜色渐变
  8. java markdown转word_Markdown 格式如何转换成 Word?
  9. SIMULIA仿真技术研讨会浙江专场
  10. HTML设计简单的教务管理系统