我们都知道,Java 是一种半编译型,半解释型的语言,其编译部分和 C++ 语言比较类似,解释部分和 Python 语言比较类似,而 Java 则是综合了两种方式的语言。
一、编译与解释
1.1 编译型语言
所谓编译,就是将程序的源代码转换成可以直接运行的二进制文件,每一个程序编译后产生的二进制文件是不同的,程序的源代码就算是只经过了轻微的更改,也要对更改的源代码文件全部重新进行编译,若源代码很多,编译速度很慢,那么调试和运行就成为了一种麻烦,从而导致程序开发效率低下,这是编译型语言的缺点。但与之相对的就是,编译后产生的二进制文件可以直接在对应的操作系统上运行,程序的各项性能都会很高。典型的编译型语言就是 C++,C++ 程序的效率非常高。为了进一步加快程序的启动速度,就发展而来了 AOT 技术。
1.2 解释型语言
所谓解释,就是由虚拟机(一种软件)对一段文本或者一个文件的内容进行分析并作出相应反应。由于解释型语言只是对文本或文件做出解释,没有编译的过程,因此,源代码文件修改了一点点,对解释器而言区别并不是很大,调试和运行速度也不会由太大的区别,进而提高了程序的开发速度,这就是解释型语言的优点。但与之相对的就是,程序的运行速度和效率会降低,因为解释器要解释的文本或文件实际上要转换成解释器可以高效解释的中间代码(有时也叫字节码),再让解释器去运行,这样的程序运行效率十分低下。典型的解释型语言就是 Python,而软件 Python 就是虚拟机。为了进一步加快程序的运行速度,就发展而来了 JIT 技术。
1.3 虚拟机
虚拟机是指用于解释程序源代码的一种软件,为什么叫虚拟机呢?因为与之相对的就是非虚拟的机器,也就是真实的机器,比如我们的电脑,这就是真实的机器,而操作系统就是运行在这个真实机器上的“虚拟机”,Java、Python 等就是“虚拟机”中的虚拟机。无论是什么程序,最终都是要变成机器码来让机器来执行的,那么在虚拟机中的虚拟机这一过程中,嵌套地越深就离真实的物理机器“越远”,程序的执行效率就越慢,因为执行过程中有层层的转换过程。我们都知道,在程序的运行效率上 C++ > Java > Python。这就是因为 C++ 离真实物理机器“近”,而 Python 离得“远”。
1.4 Java 为什么能“一次编译,到处运行”?
从上面我们可以知道,Java 是半编译、半解释型的语言,当 Java 对源代码进行编译的时候,是通过编译器 javac 将 java 文件编译成了 class 后缀的字节码文件,然后再让解释器 JVM 对 class 字节码文件进行解释,从而达到跨平台的目的。
二、JIT 与 AOT
2.1 JIT 的原理
即时编译(Just In Time)简称 JIT,即程序在运行时对热点代码进行编译,以达到加快运行速度的目的。
解释型语言想要运行得快,就只能朝着编译的方向发展,但又无法完全编译,于是就有了 JIT 技术。在解释运行的过程中,如果发现执行得非常频繁的代码(我们称之为热点代码 —— Hot Spot Code),就将这部分代码编译并缓存起来,用的时候调用编译后的文件,于是就达到了加速的目的。而我们之前提到过,Java 是半编译、半解释型的语言,当 Java 对源代码进行编译的时候,是通过编译器 javac 将 java 文件编译成了 class 后缀的字节码文件,然后再让解释器 JVM 对 class 字节码文件进行解释,从而达到跨平台的目的。这一过程中既有编译,也有解释。而在解释的过程中,JIT 就发挥它的作用了。
JIT 加速过程编辑
不仅仅在 Java 语言中有 JIT 的应用,在 Python 等语言中也有 JIT 的出现。总的来说,JIT 的吞吐量高,有运行时性能加成,程序可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发 JIT 的分层机制。
当然,Java 中 JIT 技术的应用不仅仅只有热点代码的编译,还有其他很多方面的优化。
2.2 热点代码
关于 JIT 是怎么确定哪些代码是热点代码的这个问题,下面作简单的介绍。简单地说,当一段代码被执行了很多次时,它就会被 JIT 认定为热点代码。具体地说,有两种确定方式:
基于采样的热点探测(Sample Based Hot Spot Detection)
基于计数器的热点探测(Counter Based Hot Spot Detection)
2.3 AOT 的原理
预先编译(Ahead Of Time)简称 AOT,即程序在运行之前就编译好代码,以达到加快启动速度的目的。
预先编译,也叫做静态编译,也就提前分析代码的结构,进行相应的优化后再直接编译成二进制文件,无需经过解释器解释,直接由平台运行。运行 AOT 技术的程序内存占用低,启动速度快,可以直接运行,但是无运行时的性能加成,且不能根据程序运行情况做进一步的优化。典型的就是 gcc 中的 o1、o2 以及 o3 优化了,它们就属于编译优化,提高编译后程序的质量。