第 10 章:前端编译与优化

本文最后更新于:1 年前

Java 的编译器:

  • 前端编译器:把 Java 文件转变成 Class 文件。
    • JDK 的 Javac。
    • Eclipse JDT 的增量式编译器 ECJ。
  • 即时编译器JIT 编译器,Just In Time Compiler:运行期把字节码转变成本地机器码。
    • HotSpot 虚拟机的 C1、C2 编译器。
    • Graal 编译器。
  • 提前编译器AOT 编译器,Ahead Of Time Compiler:直接把程序编译成与目标机器指令集相关的二进制代码。
    • ART 虚拟机。
    • JDK 的 Jaotc。

Java 的编译器的优化措施:

  • 【运行期】即时编译器:支撑了程序执行效率的不断提升。
    Java 虚拟机设计团队把对性能的优化全部集中到运行期的即时编译器这样可以让那些不是由 Javac 产生的 Class 文件也同样能享受到编译器优化措施所带来的性能红利
  • 【编译期】前端编译器:支撑着程序员的编码效率和语言使用者的幸福感的提高。
    • Javac 编译器:
      • 编译运行 Javac 源码:
        • Javac 源码[1]在 OpenJDK 中的位置:src\jdk.compiler\share\classes\com\sun\tools\javac
        • 复制 src\jdk.compiler\share\classes\com 的文件到项目中:
        • 修改代码访问规则,否则 Eclipse 会拒绝编译:
        • 修改编码方式,否则中文会乱码:
        • 直接运行 com.sun.tools.javac.Main::main() 方法:
        • 在 VM arguments 中设置参数,比如 --version

          就和通过命令行执行 javac --version 一样:
      • Javac 编译过程:
        • 准备:初始化插入式注解处理器
        • 解析和填充符号表:
          • 解析:
            • 词法分析:将源代码的字符流转变为标记编译时的最小元素集合。
            • 语法分析:根据标记序列构造出抽象语法树一种用来描述程序代码语法结构的树形表示方式
          • 填充符号表:产生符号地址符号信息
            • 符号表中所登记的信息在编译的不同阶段都要被用到。
        • 插入式注解处理器的注解处理。
          • 编译期对代码中的特定注解进行处理,从而影响到前端编译器的工作过程可以看作是编译器的插件,甚至包括代码注释都可以在插件中被访问到
          • 如果产生了新的符号,会返回到解析和填充符号表重新处理。
        • 分析和字节码生成:
          • 语义分析:对结构上正确的源程序进行上下文相关性质的检查。
            • 标记检查。
              【Javac 编译器对源代码做的极少量优化措施之一】常量折叠:int a = 1 + 2;编译期会把 1 + 2 折叠为 3,所以完全不会增加运行期的工作量。
            • 数据流和控制流分析。
          • 解语法糖:将语法糖不影响语言的编译结果和功能,却能更方便程序员使用该语言还原为原始的基础语法结构。
            • Java 在现代编程语言之中已经属于低糖语言
          • 字节码生成:
            • 把前面各个步骤所生成的信息转化成字节码指令写到磁盘中。
            • 少量的代码添加和转换:
              • 【代码添加】比如实例构造器 <init>() 方法、类构造器 <clinit>() 方法。
                • 代码收敛:保证无论源码中出现的顺序如何,都一定是先执行父类的实例构造器,然后初始化变量,最后执行语句块的顺序进行。
              • 【代码转换】比如字符串的操作替换为 StringBuffer或 StringBuilder 的 append() 操作。

Javac 编译过程的主要代码在 com.sun.tools.javac.main.JavaCompiler::compile():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* 1. 准备:初始化插入式注解处理器。 */
initProcessAnnotations(processors, sourceFileObjects, classnames);

// ...

/* 3. 注解处理。 */
processAnnotations(
/* 2.2. 填充符号表:产生符号地址和符号信息。 */
enterTrees(stopIfError(CompileState.ENTER, initModules(stopIfError(CompileState.ENTER,
/* 2.1. 解析:词法分析、语法分析。将源代码的字符流转变标记集合,构造出抽象语法树。 */
parseFiles(sourceFileObjects))))
),
classnames
);

// ...

if (!CompileState.ATTR.isAfter(shouldStopPolicyIfNoError)) {
switch (compilePolicy) {
// ...

case BY_TODO:
while (!todo.isEmpty())
/* 4.4. 字节码生成。 */
generate(
/* 4.3. 解语法糖。 */
desugar(
/* 4.2. 数据流和控制流分析。 */
flow(
/* 4.1. 标记检查。 */
attribute(todo.remove()))));
break;

// ...
}
}

Java 语言的语法糖:

见下一篇。

参考

  1. GitHub 可以下载到最新版本的 OpenJDK 源码:https://github.com/openjdk/jdk/tree/master/src