Javac前端编译简述

这里不讨论JIT编译、AOT编译,本文提到的编译过程仅仅指把.java文件转变为.class文件的过程,这个过程是我们最常见的,通常由Javac编译器来完成。

Javac编译器对代码的运行效率几乎没做什么优化,虚拟机设计者把对代码性能的优化集中到了后端的JIT编译器中。之所以这样设计,因为Class文件拥有虚拟机规范严格定义的通用格式,只要符合Class文件格式,就可以被虚拟机正确加载,因此不只是Java语言,其他如JRuby、Groovy等语言也可以被编译成Class文件。但不同语言使用的前端编译器(将源码文件编译成Class文件)可能是不同的,故将优化过程放到即时编译器过程,可以让不同语言的字节码都能享受到性能优化的好处。

Javac编译器本身是由Java语言编写的,Javac编译器针对程序编码过程做了很多优化措施,目的是改善程序员的编码风格和提高编码效率。

Javac编译过程

Javac编译大致分为3个过程:

  1. 解析与填充符号表
  2. 插入式注解处理器的注解处理过程
  3. 分析与字节码生成过程

整体流程如下图(图片来自《深入理解Java虚拟机》)所示

javac 编译javax javac 编译所有文件_Java

Java编译过程的主体代码如下图(图片来自《深入理解Java虚拟机》)所示

javac 编译javax javac 编译所有文件_Java_02

接下来根据上图来分析Javac编译的基本流程。

准备过程

  1. 初始化插入式注解处理器

解析与填充符号表

  1. 语法、词法分析
  1. 词法分析将源代码的字符转成标记(Token)集合,单个字符是程序编写的最小单位,而标记则是编译过程的最小单位。如“int a = b + 2”这句代码可拆分为int、a、=、b、+、2共6个标记。
  2. 语法分析是根据Token序列构造抽象语法树(AST,Abstract Syntax Tree)的过程。AST是一种用来描述程序代码语法结构的树形表示形式,语法树的每一个节点都代表着程序代码中的一个语法结构,如包、类型、修饰符、运算符、接口、返回值、代码注释等。抽象语法树建立之后,编译器基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上。
  1. 填充符号表
    符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,其中保存的信息在编译的不同阶段都要用到。以下是符号表的两个应用场景:
  1. 在语义分析中,符号表登记的内容将用于语义检查和产生中间代码。
  2. 在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。

注解处理器

  1. JDK1.5之后,Java语言提供了对注解的支持。
  2. JDK1.6中提供了一组插入式注解处理器的标准API,支持在编译期间对注解进行处理。
  3. 注解处理器可将其看做编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素,如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。
  4. 有了编译器注解处理的标准API支持,我们的代码才有可能干涉编译器的行为。

语义分析与字节码生成

语义分析的任务是对结构上正确的源程序进行上下文有关性质的审查,因为抽象语法树虽然能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。

标注检查

标注检查步骤检查的内容如变量使用前是否已经被声明、变量与赋值之间的数据类型是否匹配等。

数据及控制流分析

数据及控制流分析是对程序上下文逻辑更进一步的验证,可以检查出诸如程序局部变量是否在使用前有赋值、方法的每条路径是否都有返回值、是否所有的受检异常都被正确处理等。

解语法糖

所谓语法糖,指在计算机语言中添加某种语法,只是为了更方便程序员使用,如提高编码效率或减少出错,但对语言功能没有影响。

所谓解语法糖(desugar),是指在编译阶段将糖衣语法还原回简单的基础语法结构,因为虚拟机运行时不支持这些语法。

Java中常见的语法糖有:

  1. 泛型(JDK 1.5添加)——Java中的泛型其实是伪泛型,编译后就会被替换为原生类型了,并在相应的地方插入了强制转型代码。因此Java中的泛型实现方法也被称为类型擦除。
  2. 自动装箱、拆箱——编译后被转化成了对应的包装和还原方法。
  3. 循环遍历——编译后代码被转成了迭代器实现,这也是被遍历的类需要实现Iterable接口的原因。
  4. 变长参数——编译后实际上被转成数组类型的参数。

其他语法糖还有内部类、枚举类、断言语句、对枚举和字符串的switch支持(JDK1.7)等,可以通过跟踪Javac源码、反编译Class文件等方式了解它们的实现本质。

字节码生成

字节码生成是Javac编译过程的最后一个阶段,字节码生成阶段不仅仅是把前面各个步骤生成的信息(AST、符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。

完成了了对语法树的遍历和调整之后,就会把填充了所有所需信息的符号表交给com.sun.tools.javac.jvm.ClassWriter类,由这个类的writeClass()方法输出字节码,生成最终的Class文件,到此Javac的编译过程结束。

参考资料《深入理解Java虚拟机》