Java的编译过程是怎样的?

66

对Java编译过程感到困惑?

我知道这一点:我们编写Java源代码,平台独立的编译器将其转换为字节码,然后平台相关的JVM将其转换为机器码。

所以从一开始,我们编写Java源代码。编译器javac.exe是一个.exe文件。这个.exe文件到底是什么?难道Java编译器是用Java编写的,那么如何出现了可以执行它的.exe文件?如果编译器的代码是用Java编写的,那么在编译阶段如何执行编译器代码,因为这是JVM执行Java代码的工作。一种语言怎么能编译自己的语言代码呢?这似乎是一个先有鸡还是先有蛋的问题。

那么.class文件到底包含什么?是文本形式的抽象语法树,还是表格信息,又是什么呢?

有谁能告诉我清晰详细地,我的Java源代码是如何转换成机器码的呢?


一种语言可以轻松编译自己的语言代码。 C/C++编译器通常是用C或C++编写的,cobra语言编译器是用cobra编写的,还有许多http://en.wikipedia.org/wiki/Self-hosting编译器的例子。 - jcao219
5
编译器并非必须跨平台,只需要遵循规范指定的输入和输出即可。你甚至可以用Perl编写编译器,生成的字节码都不会在意。 - Mark Peters
相关的stackoverflow问题:https://dev59.com/KXM_5IYBdhLWcg3wzmkwJava编译器、JVM和Java是用哪种语言编写的? - jvdneste
虽然不完全相关,但值得一提的是,Sun的JVM是用C语言编写的,而Oracle的JVM(Hotspot)是用C ++编写的。 - gEdringer
9个回答

63

好的,我知道这一点:我们编写Java源代码,翻译它的编译器是平台无关的字节码。

实际上,编译器本身作为本地可执行文件运行(因此是javac.exe)。确实,它将源文件转换为字节码。字节码是平台无关的,因为它针对Java虚拟机。

然后,平台相关的jvm将其转换为机器码。

并不总是这样。对于Sun的JVM,有两个JVM: client和server。它们都可以,但不一定要编译成本地代码。

所以从一开始,我们编写Java源代码。编译器javac.exe是一个.exe文件。这个.exe文件到底是什么?Java编译器不是用Java编写的吗?那么为什么会有一个.exe文件来执行它呢?

这个exe文件是封装的Java字节码。这是为了方便——避免复杂的批处理脚本。它启动JVM并执行编译器。

如果编译器代码是用Java编写的,那么在编译阶段如何执行编译器代码,因为这是JVM执行Java代码的工作。

这正是封装代码的作用。

一种语言如何编译自己的语言代码?这似乎对我来说就像鸡和蛋的问题。

确实,乍一看令人困惑。不过,这不仅仅是Java的习惯用法。Ada的编译器也是用Ada本身编写的。它看起来像是一个“鸡和蛋的问题”,但事实上,这只是一个引导问题。

现在,.class文件到底包含什么?它是以文本形式的抽象语法树吗?它是表格信息吗?是什么?

它不是抽象语法树。AST只在编译时由tokenizer和compiler用于表示内存中的代码。 .class文件类似于JVM的汇编代码。JVM则是一台抽象机器,可以运行专门针对虚拟机的机器语言。最简单的.class文件与普通汇编有非常相似的结构。开头声明了所有静态变量,然后是一些外部函数签名的表,最后是机器码。

如果您真的很好奇,可以使用“javap”实用程序探索classfile。这是调用javap -c Main的样本(已混淆)输出:

0:   new #2; //class SomeObject
3:   dup
4:   invokespecial   #3; //Method SomeObject."<init>":()V
7:   astore_1
8:   aload_1
9:   invokevirtual   #4; //Method SomeObject.doSomething:()V
12:  return

所以你应该已经有一个想法它到底是什么了。

有人可以清楚详细地告诉我,我的 Java 源代码是如何转换为机器码的吗?

我认为现在应该更清楚了,以下是简短的总结:

  • 您需要使用 javac 命令指向您的源代码文件。Javac 内部的读取器(或标记解析器)会读取您的文件并构建出实际的 AST。所有语法错误都来自于此阶段。

  • javac 的工作还没有完成。当它有了 AST 时,真正的编译才能开始。它使用访问者模式来遍历 AST 并解析外部依赖项,以添加意义(语义)到代码中。最终产物将保存为包含字节码的 .class 文件。

  • 现在是运行程序的时候了。您需要使用 java 命令并指定 .class 文件的名称。现在 JVM 再次启动,但这次是为了“解释”您的代码。如果需要,JVM 可能会将您的抽象字节码编译为本机汇编代码。Sun HotSpot 编译器与即时编译相结合,如果需要的话,可能会这样做。正在运行的代码不断被 JVM 进行性能分析,并在满足某些规则时重新编译为本机代码。通常情况下,首先要编译成本机代码的是“热门”代码。

编辑:如果没有 javac,那么您必须使用类似于以下内容的编译器来进行编译:

%JDK_HOME%/bin/java.exe -cp:myclasspath com.sun.tools.javac.Main fileToCompile

正如你所看到的,它调用了 Sun 的私有 API ,因此它将绑定于 Sun JDK 实现。这会使构建系统依赖于它。如果一个人切换到任何其他 JDK(维基列出了除 Sun 之外的五个 JDK),则上述代码应该更新以反映更改(因为编译器不太可能驻留在 com.sun.tools.javac 包中)。其他编译器可以用本地代码编写。

因此,标准方式是随 JDK 发送 javac 包装器。


2
所以根据你的描述,这是情景:我们编写源代码。javac.exe可执行文件主要用于收集参数并启动JVM。JVM然后执行编译器(大量已编译的.class文件的集合)代码。编译器然后执行我们编写的程序。此外,在设计编译器时,使用了其他语言(在我们的情况下是C)来创建编译器的.class文件。我说的对吗?.exe文件是封装的Java字节码吗?它并不完全是封装代码。你在谈论什么样的复杂批处理脚本? - nash
3
正确的说法是,javac只是一个方便的包装器,真正的编译器代码在Sun JDK的com.sun.tools.javac包中。你可以到那里查看源代码-了解内部机制可能会很有趣。你也可以直接从Java中调用javac编译器,而无需调用任何外部进程。如果你考虑到这一点,就有意义了,因为javac只是启动JDK内部某个类文件的启动器。我只能猜测,但我相信它几乎什么都不做,只是启动JVM并传递参数。 - Rekin
关于批处理脚本,请看答案的最后一段。我编辑了它以回答您的评论。 - Rekin
好的,非常感谢您为清晰的回复所付出的努力。 - nash
一个分词器用于分词。它不会产生AST或语法错误。你不知道javac是否使用访问者模式。 - user207421
1
@EJP,那些是阅读OpenJDK的javac源代码时所做的笔记。 - Rekin

16
Java编译器难道不是用Java编写的吗?那么为什么存在可执行文件 .exe 来执行它呢?
您从哪里得到这些信息的?javac可执行文件可以用任何编程语言编写,这并不重要,重要的是它是一个可执行文件,将.java文件转换为.class文件。
有关 .class 文件的二进制规范的详细信息,您可能会发现Java 语言规范中的这些章节很有用(尽管可能有点技术含量):
- 虚拟机启动 - 类和接口的加载 您还可以查看虚拟机规范,其中包括:
- class 文件格式 - Java 虚拟机指令集 - 为 Java 虚拟机编译

会查看这些链接。还有我在Rekin的回答下添加的场景评论。我的理解正确吗? - nash

11

编译器javac.exe是一个.exe文件。那么这个.exe文件到底是什么呢?Java编译器不是用Java语言编写的吗?那它为何要有一个执行它的.exe文件呢?

Java编译器(至少是Sun/Oracle JDK中的编译器)的确是用Java语言编写的。 javac.exe只是一个启动器,它处理命令行参数,其中一些参数传递给运行编译器的JVM,其他参数传递给编译器本身。

如果编译器代码是用Java编写的,那它是如何在编译阶段被执行的呢?毕竟它是JVM的工作来执行Java代码。一种语言怎么可能编译自己的语言代码呢?这似乎就像先有鸡还是先有蛋的问题。

许多(如果不是大多数)编译器都是用它们所编译的语言编写的。显然,在早期阶段,编译器本身必须由其他东西编译,但是在“引导”之后,任何新版本的编译器都可以由旧版本编译。

现在.class文件到底包含什么?它是一种文本形式的抽象语法树,还是表格信息,还是其他什么呢?

类文件格式的详细信息在Java虚拟机规范中描述。


Java编译器(至少是Sun/Oracle JDK附带的那个)确实是用Java编写的。 我不知道这个!谢谢您,先生。 - ZoFreX

6

javac和jvm通常是本地二进制文件。它们是用C或其他语言编写的。当然,也可以用Java编写它们,只是需要先有本地版本。这称为“引导”。

有趣的事实:大多数编译器将代码编译成本地代码都是用它们自己的语言编写的。但是,它们都必须先用另一种语言(通常是C)编写一个本地版本。相比之下,第一个C编译器是用汇编语言编写的。我推测第一个汇编器是用机器码编写的。(或者,使用蝴蝶;)

.class文件是由javac生成的字节码。它们不是文本格式,而是类似于机器码的二进制代码(但具有不同的指令集和架构)。

jvm在运行时有两个选项:它可以解释字节码(假装自己是CPU),也可以将其JIT(即时)编译成本地机器码。后者更快,但更复杂。


谢谢分享XKCD漫画..我还没看过 :) - David J. Liszewski
从技术上讲,第一个编译器可以通过用其他语言编写的解释器创建,尽管解释和JIT编译之间的界限有点模糊,因为它们最终都会产生本机机器代码。 - Andrzej Doyle
真的。事实上,甚至还有更疯狂的选择。第一个C++编译器只是一个翻译器,它生成用于C编译器的C代码。 - Mike Caron
咦?为什么有人踩了我的回答?是我的回答有问题吗? - Mike Caron
1
@Mike:嗯,javac不是用C写的,而是用Java写的,所以你的第一句话并不完全正确。否则,回答很好:-)。 - sleske
@sleske javac是用Java编写的吗?我其实不知道。但这很有道理。但是,我敢打赌javac 1.0不是用Java编写的 ;) - Mike Caron

3

.class文件包含的是字节码,有点像高级汇编。编译器可以完全使用Java编写,但为了避免“先有鸡还是先有蛋”的问题,JVM必须编译成本地代码。我认为它是用C语言编写的,标准库的低层也是如此。当JVM运行时,它执行即时编译,将字节码转化为本地指令。


3

简短解释

在文本编辑器上编写代码,以编译器能够理解的格式保存——".java" 文件扩展名,javac(Java 编译器)将其转换为".class" 格式文件(字节码 - 类文件)。JVM 在它所在的操作系统上执行 .class 文件。

详细解释

始终要记住 Java 不是操作系统认可的基础语言。Java 源代码由称为 Java 虚拟机(JVM) 的翻译器解释给操作系统。JVM 无法理解您在编辑器中编写的代码,它需要编译后的代码。这就是编译器发挥作用的地方。

每个计算机进程都涉及内存操作。我们不能只在文本编辑器中编写代码并进行编译。我们需要将其放入计算机的内存中,即在编译之前将其保存。

javac(Java 编译器)如何识别保存的文本以进行编译?——我们有一种单独的文本格式,编译器可以识别,即.java。将文件保存为 .java 扩展名,编译器将识别它并在请求时对其进行编译。

编译时会发生什么?——编译器是该过程中涉及的第二个翻译器(不是技术术语),它将用户理解的语言(Java)翻译为 JVM 理解的语言(字节码 - .class 格式)。

编译后会发生什么?——编译器产生了 JVM 可以理解的 .class 文件。然后执行程序,即 JVM 在操作系统上执行 .class 文件。

你应该知道的事实

1)Java 不是多平台的,而是跨平台的。

2)JVM 是使用 C/C++ 开发的。这是人们称 Java 为比 C/C++ 更慢的语言的原因之一。

3)Java 字节码(.class)是用"汇编语言"编写的,这是 JVM 唯一能理解的语言。任何生成 .class 文件或生成的字节码的代码都可以在 JVM 上运行。


1

在安装Java运行时之前,Windows不知道如何调用Java程序,而Sun选择使用本地命令来收集参数,然后调用JVM,而不是将jar后缀绑定到Java引擎。


据我所知,在CMD.EXE中不行。你不能仅仅在命令行上输入“foobar.jar”并让它执行。这可能是Windows 95/98/ME中COMMAND.EXE的限制,导致做出了这个决定。不过现在这样做会很方便。 - Thorbjørn Ravn Andersen

-1

编译器最初是用C语言编写的,其中有一些C++代码,我认为现在仍然是这样(你为什么认为编译器也用Java编写了呢?)。javac.exe只是编译器的C/C++代码。

顺便说一下,你可以用Java编写编译器,但你必须避免鸡生蛋蛋生鸡的问题。为了解决这个问题,你通常会使用类似于C语言的一个或多个引导工具来编译编译器。

.class文件包含字节码,是javac编译过程的输出,这些指令告诉JVM该做什么。在运行时,这些字节码被翻译成本地CPU指令(机器码),以便它们可以在JVM下的特定硬件上执行。

稍微复杂一点的是,JVM还会优化和缓存从字节码生成的机器码,以避免重复翻译它们。这被称为JIT编译,并且在程序运行时和字节码被解释时发生。


2
众所周知,(Sun/Oracle) Java编译器是用Java编写的。自Java 6以来,甚至有一个官方API可供使用,但在此之前,人们通过sun.tools包中的非官方API进行调用。 - Michael Borgwardt

-4
  1. .java文件
  2. 编译器(JAVA BUILD)
  3. .class文件(字节码)
  4. JVM(通常使用'C'语言构建的系统软件)
  5. 操作平台
  6. 处理器

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接