从源代码到机器代码,Java语言中发生了什么?

在上一篇文章中,我们讨论了无论代码是用什么语言编写的,它最终都毫无例外地运行在机器代码中。那么Java语言中发生了什么,从源代码到机器代码?这就是我们今天要讨论的。

如下图所示,编译器可以分为前端编译器、JIT编译器和AOT编译器。我们一个接一个地谈吧。

前端编译器:源代码到字节码

正如我们前面所说的,对于Java虚拟机,实际输入是字节码文件,而不是Java文件。那么,实际上如何将Java代码转换为Java语言的字节码文件呢?我们知道JDK安装目录中有一个JavaC工具,它将Java代码转换成字节码。这个工具叫做编译器。与后面提到的其他编译器相比,它们还处于编译的早期阶段,因此称为前端编译器。

 

通过Javac编译器,我们可以很容易地将Java源文件翻译成字节码文件。以Hello World为例:

public class Demo{
   public static void main(String args[]){
        System.out.println("Hello World!");
   }
}

当我们使用javac命令编译上面的类时,我们生成一个Demo.类文件:

> javac Demo.java
> ls 
Demo.java Demo.class

当我们使用纯文本编辑器打开演示时。类文件,我们将发现一系列16位的二进制流。

运行Javac命令的过程实际上是解析Java源代码并通过Javac编译器生成字节码文件的过程。直截了当地说,Java语言规范实际上是通过使用Javac编译器转换成字节码语言规范的。javac编译器的过程可以分为以下四个阶段:

第一阶段是词汇和语法分析。在这个阶段,JVM扫描源代码的字符一次,最后生成一个抽象语法树。简单地说,在这个阶段,JVM将理解我们的代码真正想要做什么。正如我们分析一个句子一样,我们将它分为主语、谓语和宾语,并找出句子的意思。

扫描二维码关注公众号,回复: 4606507 查看本文章

第二阶段:填写符号表。我们知道类彼此引用,但是在编译阶段,我们不能确定它们的特定地址,所以我们使用符号代替。我们在这个阶段所做的是类似的,即抽象类或接口的符号填充。加载类时,JVM用特定的内存地址替换符号。

第三阶段:注释处理。我们知道Java支持注释,所以在这个阶段,注释将根据注释的作用被分析并恢复到特定的指令集。
第四阶段:分析和字节码生成。在这个阶段,JVM将基于以上阶段的结果生成字节码,并且最终输出是一个类文件。

我们一般称 javac 编译器为前端编译器,因为其发生在整个编译的前期。常见的前端编译器有 Sun 的 javac,Eclipse JDT 的增量式编译器(ECJ)。

JIT 编译器:从字节码到机器码

当源代码转换为字节码时,有两种选择可以运行程序。一种是使用Java解释器来解释执行字节码,另一种是使用JIT编译器将字节码转换成本地机器代码。

两种方法的区别在于前者启动速度快,但运行速度慢,而后者启动速度慢,但运行速度快。原因很简单。因为解释器不需要像JIT编译器那样将所有字节码转换为机器代码,所以它自然地减少了优化时间。当JIT编译器完成其第一次编译时,它将保存与字节代码对应的机器代码,并且可以在下次直接使用。正如我们所知,机器代码必须比Java解释器更有效。因此,在实践中,为了更快、更有效地运行,我们通常结合这两种方法编译和执行Java代码。

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

注意:现在很多人都称客户端编译器C1编译器和服务器编译器C2编译器,但他们在Oracle官方文档中将其描述为编译器模式。所以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版本,以查看我的机器上的虚拟机以混合模式运行。

到目前为止,我们已经学习了从Java源代码到字节码的整个过程,然后从字节码到机器代码。这应该已经在这里结束了,但是在我们的Java中有一个AOT编译器,它将源代码直接转换成机器代码。

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即时编译器,最常见的是热点虚拟机中的客户端编译器和服务器编译器,将Java字节码编译成本地机器代码。AOT编译器可以直接将源代码编译为本地机器代码。这三个编译器的编译速度和质量如下:

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

在JVM中,通过这些不同方式的协作,可以优化JVM的编译质量和运行速度。

猜你喜欢

转载自blog.csdn.net/qq_41701956/article/details/85014038
今日推荐