对于每一位 Javer
而言,想必对于 JVM
都并不陌生,即使在实际开发中并没有深入研究,但或多或少对其仍有一定的了解。
所谓 JVM
缩写于 Java Virtual Machine
即 Java
虚拟机,所以开发的程序都是运行于此虚拟机之前,也是 Java
引以为傲的特性即一次编译任意运行,作为应用程序于操作系统之间沟通的桥梁,只需安装了 JRE
环境即可跨平台运行。
1. JIT编译
若提到 JVM
,那必然绕不开 JIT(Just In Time)
编译,故名思意即时编译。
我们都知道开发的应用程序想要运行在 JVM
之上,需要先将 .java
文件编译为 .class
字节文件。当编译完成之后,程序的字节文件则可在任意的 JVM
环境上运行,由 JVM
负责解析字节文件交由操作系统执行。
让我们将目光聚焦到 JVM
与操作系统交互上,操作系统并不认识编译之后的 .class
文件,需要由 JVM
承担其转译的工作,但通过此方式虽达到了效果但性能却并不令人满意。
那有什么方式能够解决呢?最简单的方式即将编译后的字节文件再次转译为操作系统可识别的底层汇编机器码,则操作系统可直接进行执行,省去了 JVM
解释的这一动作,而这个过程即称为 JIT
编译。
2. 编译类型
简单来讲,JIT
的工作即将字节文件转化为操作系统可直接执行的机器码。
在之前介绍垃圾回收器的时候提到过程序的启动支持 Client
与 Server
两种模式,而同样 JIT
对应的也有 C1
与 C2
模式。
二者的区别在于 Server
模式即 C2
相对于 C1
而言在解析编译为机器码时做出更多的编译优化,对于长期运行于服务器上的引用而言相对更为合适。
3. 编译优化
但在具体的场景中相对更为复杂,两种编译模式更多的是搭配进行。
当我们将编写程序编译为字节文件时,此时编译执行的策略是 C1
模式,即与实际编写的代码并无差异。而在 JVM
实时运行过程中,当 JVM
检测到某一代码块的执行频率提高时,则会动态基于 C2
模式实时调整优化,这也是即时编译名称的由来。
对于 JIT
编译的优化可谓门道颇深,在 《Effective Java》
第 66
节中也提到 JIT
优化中的一种 hoisting
即优化提升,感兴趣的可自行查看原文。
这里举个示例进行演示:
public static void main(String[] args) {
for(int i = 1; i < 10*100; i++) {
System.out.println(i);
}
}
上述示例中循环执行了 1000
次打印输出,按照直觉而言 for
循环结束判断的表达式 10*100
每次循环都执行计算一次,但实际上并非如此。
正是由于 JIT
编译优化的存在的,实际运行生效的结果将为下述代码,即 hoisting
优化提升会将计算前置,从而整个循环过程计算只会执行一次。
public static void main(String[] args) {
int count = 10*100;
for(int i = 1; i < count; i++) {
System.out.println(i);
}
}
想要了解更多的推荐去看周志明老师出版的 《深入理解 Java 虚拟机》
,在第 11
章详细介绍了 JIT
内容。
4. GraalVM
那讲了这么多 JIT
究竟和 GraalVM
又有什么关系呢?
我们都知道 Java
已诞生发展数十年,且随着技术的不断演进想要在原有的 JIT
基础之上提出更多的特性以及优化所需要付出成本是十分高昂的,那最简单的方式就是推到重来。
这也是 GraalVM
所诞生的由来,同时不同与 JVM
的有 C++
实现,GraalVM
更是实现了自举即其通过 Java
进行开发,属于套娃了。
GraalVM
同样具备 C2
中编译优化特性,同时引入新的虚拟机接口规范 JVMCI(JVM Compiler Interface)
,拥有更良好的设计规范从而降低维护成本。
更多的 GraalVM
信息这里就不再详细展开了,如果感兴趣的可以去官网或 GitHub
仓库上查看具体的设计实现:GraalVM GitHub。
参考链接