3种常见的编译过程
  • 前端编译器
  • 把 *.java 文件转变成 *.class 文件
  • JDK 的 Javac、 Eclipse JDT 中的增量式编译器(ECJ)
  • 即时编译器
  • 运行期把字节码转变成本地机器码
  • HotSpot 虚拟机的 C1、 C2 编译器, Graal 编译器
  • 提前编译器
  • 把程序编译成与目标机器指令集相关的二进制代码
  • JDK 的 Jaotc、 GNU Compiler for the Java(GCJ)、 Excelsior JET
Javac编译过程
  • 准备过程
  • 初始化插入式注解处理器
  • 解析与填充符号表过程
  • 词法、语法分析
  • 将源代码的字符流转变为标记集合,构造出抽象语法树
  • 填充符号表,产生符号地址和符号信息
  • 插入式注解处理器注解处理过程

  • 分析与字节码生成过程

  • 标注检查,对语法静态信息进行检查
  • 数据流及控制流分析,对程序动态运行过程进行检查
  • 解语法糖,将简化代码编写的语法糖还原为原有形式
  • 字节码生成
  • Javac编译过程
  • Javac 编译动作入口 com.sun.tools.javac.main.JavaCompiler 类中 compile () 和 compile2 () 方法主体代码
  • 主体代码

# 解析与填充符号表(parseFiles () 方法)

# 词法、语法分析

  • 词法分析
  • 将源代码的字符流转变为标记(Token)集合的过程。
  • 标记是编译时最小元素。
  • javac 中由 com.sun.tools.javac.parser.Scanner 类来实现该过程。
  • 语法分析
  • 根据标记序列构造抽象语法树的过程。
  • 抽象语法树使用树形表示方式来描述代码语法结构。
  • 抽象语法树每一个节点都代表程序代码中的一个语法结构。(例如:包、类型、修饰符等)。
  • javac 中由 com.sun.tools.javac.parser.Parser 类实现该过程,产生以 com.sun.tools.javac.tree.JCTree 类表示的抽象语法树。
  • 编译器后续操作都建立在抽象语法树上(不再操作源码字符流)。

# 填充符号表(enterTrees () 方法)

  • 符号表(Symbol Table)是由一组符号地址和符号信息构成的数据结构。(有序符号表、树状符号表、栈结构符号表等)

符号表的信息在编译不同阶段都被用到:

  • 语义分析时,用于语义检查
  • 是地址分配的直接依据
  • javac 中使用 com.sun.tools.javac.comp.Enter 类实现填充符号表的过程。
  • 该过程产生一个待处理列表,包含了每一个编译单元的抽象语法树的顶级节点(还可能有 package-info.java 的顶级节点)。

# 注解处理器

  • JDK6 时设计出 “插入式注解处理器”,将对代码中的特定注解提前至编译器进行。
  • 插入式注解处理器工作时,允许读取、修改、添加抽象语法树中的任意元素。
  • 如果在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到语法树不再修改为止,每一次循环过程成为一个轮次(Round)。
  • 应用实例:Lombok 工具
  • javac 中在 initPorcessAnnotations () 方法中完成插入式注解处理器的初始化过程。
  • 在 porcessAnnotations () 方法中完成执行过程。并判断是否有新的注解处理器需要执行,并通过 com.sun.tools.javac.processing.JavacProcessing-Environment 类的 doProcessing () 方法
    来生成一个新的 JavaCompiler 对象, 对编译的后续步骤进行处理。

# 语义分析与字节码生成

  • 主要任务是对结构上正确的源程序进行上下文相关性质的检查。(类型检查、控制流检查、数据流检查等)

# 标注检查(attribute () 方法)

  • 比如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。
  • 标注检查中会进行常量折叠的代码优化(比如代码中定义 int a = 1+2,在抽象语法树上能看到字面量 "1""2",和操作符"+", 经常量折叠优化后,被折叠为字面量"3"(ConstantExpressionValue: 3))
  • javac 中 com.sun.tools.javac.comp.Attr 类和 com.sun.tools.javac.comp.Check 类为该过程实现类。

# 数据及控制流分析 (flow () 方法)

  • 对程序上下文逻辑进一步验证(比如:程序局部变量在使用前是否有赋值、方法每条路径是否都有返回值等)。
  • javac 中由 com.sun.tools.javac.comp.Flow 类来完成操作。

# 解语法糖

  • 语法糖对语言编译结果和功能没有实际影响。
  • 能够减少代码量、增加程序可读性,从而减少代码出错机会。
  • javac 中由 desugar () 方法触发该过程,在 com.sun.tools.javac.comp.TransTypes 类和 com.sun.tools.javac.comp.Lower 类中完成。
java语法糖详解
  • 泛型
  • 参数化类型、参数化多态。
  • java 为 “类型擦除式泛型”(类型擦除),c# 为 “具现化式泛型”。
  • 使用 Signature 属性存储一个方法在字节码层面的特征签名,用于保存参数化类型信息(泛型)。
  • 擦除法仅仅是对方法的 Code 属性中字节码进行擦除,实际上元数据中仍保留泛型信息(因此可以通过反射手段取得参数化类型)。
  • 自动拆装箱、遍历循环
  • 自动装箱、拆箱在编译之后被转化成对应的包装和还原方法(如:Integer.valueOf () 和 Integer.intValue ())。
  • 遍历循环把代码还原成迭代器的实现(因此被遍历的类需要实现 Iterable 接口)。
  • 条件编译
  • C、C++ 使用预处理器指示符(#ifdef)来完成条件编译。
  • java 语言将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,各个文件之间能够互相提供符号信息,无需使用预处理器。
  • java 使用条件为常量的 if 语句进行条件编译。

# 字节码生成

  • 生成字节码完成 javac 编译过程。
  • javac 中由 com.sun.tools.javac.jvm.Gen 类来完成。
  • 除了将之前各步骤生成的信息转化成字节码指令写入磁盘,还进行少量代码添加和转换工作(例如:<init>() 和 < clinit>())。
  • com.sun.tools.javac.jvm.ClassWriter 类输出字节码完成编译。