好的,我知道这一点:我们编写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
3: dup
4: invokespecial
7: astore_1
8: aload_1
9: invokevirtual
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
包装器。