宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取

JVM基础系列第4集:从源代码到机器码,发生了什么? 对Java语言来说,从源代码到机器码,中间发生了什么呢?

如下图所示,编译器为前端编译器、JIT 编译器和AOT编译器.

1 .前端编译器:在将源代码转换为字节码之前,对于Java虚拟机,实际输入的是字节码文件,而不是Java文件。 那么,在Java语言中,实际上是如何将Java代码转换为字节码文件的呢? 我们知道JDK的安装目录中有一个javac工具,用于将javac代码翻译成字节码。 这个工具称为编译器。 相对于后述的其他编译器,由于处于编译的前期,所以成为前端编译器。

使用javac编译器,可以轻松地将java源文件翻译为字节码文件。 以我们最熟悉的Hello World为例:

public class demo { publicstaticvoidmain string args [ ] } { system.out.println ‘ hello world!’ ); }使用javac命令编译上面的类时,将生成Demo.class文件。

使用javac demo.javalsdemo.Java demo.class纯文本编辑器打开demo.class文件时,可以看到它是一系列十六进制流。

我们执行javac命令的过程实际上是javac编译器解析Java源代码并生成字节码文件的过程。 简言之,其实是使用javac编译器将Java语言规范转换为字节码语言规范。 javac编译器的处理分为四个阶段:

第一阶段:词法、语法分析。 在此阶段,JVM将扫描源代码中的字符一次,最终生成抽象语法树。 简而言之,在这个阶段,JVM会理解我们的代码想做什么。 就像我们分析一个句子一样,我们把主谓宾分在句子里,阐明这个句子应该表达的意思。

第二阶段:填写符号表。 我们知道类之间会互相引用,但在编译阶段无法确定具体地址,所以我们改用符号。 这个阶段所做的就是在抽象的类和接口中嵌入符号。 等到类加载后,JVM会用特定的内存地址替换符号。

第三阶段:评论处理。 因为我们知道Java支持注释,所以在此阶段分析注释,并根据注释的作用将其恢复到特定的指令集。

第四阶段:分析和字节码生成。 到了这个阶段,JVM将根据上述几个阶段的分析结果生成字节码,并最终输出到class文件中。

javac编译器通常被称为前端编译器。 因为它发生在整个编译的前期。 典型的前端编译器包括Sun的javac和Eclipse JDT的增量编译器ECJ )。

2. JIT编译器:源代码从字节码到机器码)转换为字节码后,实际运行程序有两种选择。 一种是使用Java解释器解释可执行字节码,另一种是使用JIT编译器将字节码转换为本地机器码。

这两种方式的区别在于,前者启动速度快但执行速度慢,而后者启动速度慢但执行速度快。 要说为什么会这样,其理由很简单。 解释器不需要像JIT编译器那样将所有字节码转换为机器码,因此自然优化的时间更少。 JIT编译器在第一次编译完成后,保存字节码对应的机器码,下次可以直接使用。 而且,我们知道机器码的执行效率一定比Java解释器高。 因此,实际上,为了执行速度和效率,一般将两者组合起来进行Java代码的编译执行。

HotSpot虚拟机包含两个即时编译器:客户端编译器和服务器编译器。 这两个不同的编译器派生出两种不同的编译模式,分别称为C1编译模式、C2编译模式。

注:现在很多人都习惯将客户端编译器称为C1编译器,将服务器编译器称为C2编译器,但Oracle的官方文档中将它称为compiler模式。 所以说C1编译器、C2编译器是我们自己的习惯称谓,不是官方说法。 这一点需要特别注意。

那么 C1 编译模式和 C2 编译模式有什么区别呢?

C1编译模式提供简单、可靠的优化,将字节码编译成本地代码,并根据需要添加性能监视逻辑。 在C2编译模式下,将字节码编译成本地代码,但启用了需要较长时间的编译优化,并根据性能监视信息进行不可靠的极端优化。

简单来说,基于C1编译模式的优化比较保守,其编译速度比C2快。 另一方面,C2编译模式进行了过度优化,并根据性能监视进行了针对性优化,因此编译质量比较好,但需要时间。

>

那么到底应该选择 C1 编译模式还是 C2 编译模式呢?

实际上对于 HotSpot 虚拟机来说,其一共有三种运行模式可选,分别是:

混合模式(Mixed Mode) 。即 C1 和 C2 两种模式混合起来使用,这是默认的运行模式。如果你想单独使用 C1 模式或 C2 模式,使用 -client 或 -server 打开即可。解释模式(Interpreted Mode)。即所有代码都解释执行,使用 -Xint 参数可以打开这个模式。编译模式(Compiled Mode)。 此模式优先采用编译,但是无法编译时也会解释执行,使用 -Xcomp 打开这种模式。

在命令行中输入 java -version 可以看到,我机器上的虚拟机使用 Mixed Mode 运行模式。

            

写到这里,我们了解了从 Java 源代码到字节码,再从字节码到机器码的全过程。本来到这里就应该结束了,但在我们 Java 中还有一个 AOT 编译器,它能直接将源代码转化为机器码。

3. AOT 编译器:源代码到机器码

AOT 编译器的基本思想是:在程序执行前生成 Java 方法的本地代码,以便在程序运行时直接使用本地代码。

但是 Java 语言本身的动态特性带来了额外的复杂性,影响了 Java 程序静态编译代码的质量。例如 Java 语言中的动态类加载,因为 AOT 是在程序运行前编译的,所以无法获知这一信息,所以会导致一些问题的产生。类似的问题还有很多,这里就不一一举例了。

总的来说,AOT 编译器从编译质量上来看,肯定比不上 JIT 编译器。其存在的目的在于避免 JIT 编译器的运行时性能消耗或内存消耗,或者避免解释程序的早期性能开销。

在运行速度上来说,AOT 编译器编译出来的代码比 JIT 编译出来的慢,但是比解释执行的快。而编译时间上,AOT 也是一个始终的速度。所以说,AOT 编译器的存在是 JVM 牺牲质量换取性能的一种策略。就如 JVM 其运行模式中选择 Mixed 混合模式一样,使用 C1 编译模式只进行简单的优化,而 C2 编译模式则进行较为激进的优化。充分利用两种模式的优点,从而达到最优的运行效率。

总结

在 JVM 中有三个非常重要的编译器,它们分别是:前端编译器、JIT 编译器、AOT 编译器。

前端编译器,最常见的就是我们的 javac 编译器,其将 Java 源代码编译为 Java 字节码文件。JIT 即时编译器,最常见的是 HotSpot 虚拟机中的 Client Compiler 和 Server Compiler,其将 Java 字节码编译为本地机器代码。而 AOT 编译器则能将源代码直接编译为本地机器码。这三种编译器的编译速度和编译质量如下:

编译速度上,解释执行 > AOT 编译器 > JIT 编译器。编译质量上,JIT 编译器 > AOT 编译器 > 解释执行。

而在 JVM 中,通过这几种不同方式的配合,使得 JVM 的编译质量和运行速度达到最优的状态。

来源:https://www.cnblogs.com/chanshuyi/p/jvm_serial_04_from_source_code_to_machine_code.html