侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

JIT - 即时编译

barwe
2021-06-28 / 0 评论 / 0 点赞 / 886 阅读 / 1,452 字
温馨提示:
本文最后更新于 2022-07-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

即时编译

编译器和解释器

静态编译(static compilation):也叫 事前编译(ahead-of-time compilation, AOT),在程序运行前将所有代码编译成 机器码

动态编译(dynamic compilation):与静态编译完全相反,只在程序运行时才进行编译,编译的结果不会被保留。

即时编译(just-in-time compilation):在程序运行期间,如果遇到 热点代码块,将其编译成 机器码,这样下次遇到这个代码块可以直接执行机器码,而不用重新编译。即时编译是一种特殊的动态编译,即在纯动态编译基础上对 热点代码块 做了编译后机器码缓存的优化。

解释器:直接执行代码得到结果,适用于需要快速启动和执行的程序。

编译器:将代码编译成机器码,执行效率更高,但是需要额外的编译时间。

JIT 中,编译器需要额外的编译时间,编译的结果需要额外存放在内存里。

编译的时间开销

解释器 可以抽象为:代码 → [解释器] → 结果

JIT编译器 可以抽象为:代码 → [JIT编译器] → 机器码 → [执行] → 结果

JIT编译执行比解释器执行多了一个步骤,因此整体上JIT编译 慢于 解释器执行,但是对于 热点代码块,一旦进行JIT编译,之后再次执行都是直接执行的编译后的 机器码,其速度要 快于 直接用解释器执行。因此,只有对需要频繁执行的代码,JIT编译才能保证有正面的收益。

编译的空间开销

使用 javac 可以将 java 源码编译成 字节码,文件后缀为 .class

字节码能够直接被 java虚拟机 读取并执行,是一种与平台无关的二进制数据。

非java家族的语言可以将自己的代码转换为字节码,从而在java虚拟机上执行。

在JIT编译中,如果将 字节码 完全编译成 机器码,其体积会显著变大,会大大消耗内存。因此 JVM 总是将 解释器JIT编译器 结合起来使用。

什么是热点代码

热点代码 是指在程序运行过程中会被多次执行的代码:

  • 被多次调用的 函数 或者 方法
  • 被多次执行的循环体

栈上替换(On Stack Replacement, OSR):编译发生在方法执行的过程中,此时 方法栈帧 还在栈上,但是方法的内容被替换掉了。

热点代码的判定

需要有一种标准用于在程序运行过程中判断哪些代码是热点代码,从而触发 JIT编译,即 热点探测(Hot Spot Detection)。热点探测的主要方式有:

  • 周期性的检查各个线程的栈顶,经常出现在栈顶的方法即为热点方法
    • 优点:简单高效;通过展开堆栈可以很容易获取方法的调用关系
    • 缺点:很难精确的量化方法的热度,因为线程阻塞或者外界环境波动会影响方法热度的判定
  • 为每个方法建立 计数器,统计方法的执行次数
    • 优点:统计结果更加精确和严谨
    • 缺点:实现方式更加复杂;不能获取到方法的调用关系

HotSpot 虚拟机 使用的是 计数器判定法,它为每个方法准备了两个计数器,当计数器之和达到某个阈值时触发 JIT编译

  • 方法调用计数器:统计方法被调用的次数
  • 回边计数器:统计方法中 循环体代码块 执行的次数,回边 指的是 字节码 中遇到控制流向后跳转的指令

计数器之和达到设定的阈值 时,虚拟机会向 JIT编译器 提交一个编译请求,这个请求是异步的,虚拟机会以 解释执行 的方式继续执行,直到下一次再次执行该代码时,会检查该方法是否已经被编译。

编译的两种模式

HotSpot虚拟机 提供了两种编译模式:

  • Client Compiler:注重于局部优化,简单快速
  • Server Compiler:注重于全局优化,耗时,面向服务器

参考资料

整理自 https://www.cnblogs.com/dzhou/p/9549839.html

0

评论区