早期编译优化——Javac编译器

96
JESiller
2018.04.27 11:14 字数 1517

本身是由Java语言编写的,编译过程大致分为以下三个过程

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

1.解析与填充符号表

1.1词法、语法分析

词法分析是将源代码字符流转变为标记(Token)集合,单个字符是代码 编写的时候最小的元素,而标记是编译时最小的元素,关键字、变量名、字面量、运算符都可以成为标记。

词法分析是根据标记(Token)序列构造抽象语法树的过程,抽象语法树是一种用来描述程序代码语法结构的属性表示方式,抽象语法树的每一个节点都代表着程序代码中的一个语法结构。例如 :包,类型,修饰符,运算符,接口,返回值甚至注释等都可以是一个语法结构。

1.2填充符号表

符号表是一组由符号地址和符号信息构成的表格,符号表中所登记的信息在编译的不同阶段都要用到,语义分析中,符号表登记的信息用于语义检查和生成中间代码,在目标代码生成阶段,当对符号进行地址分配时,符号表时 地址分配的依据。

2.注解处理器

在JDK1.5之后,Java语言提供了对注解(Annotation)的支持,这些注解与代码一样在运行期发挥作用,JDK6中实现了JSR-269规范,提供了一组插入式注解处理器的标准API在编译期对注解进行处理,我们可以把它看作是一组编译器的插件,在这些插件里面可以读取、修改、添加抽象语法树中的任意元素,如果这些插件对抽象语法树进行了修改,编译器将会回到解析与填充符号表的过程重新处理,直到所有插入式注解处理器没有在对抽象语法树进行任何修改,每一次循环称为一个Round。

3.语义分析和字节码生成

语法分析之后编译器获得了程序代码的抽象语法树表示,语法树表示一个结构正确的源程序的抽象,但是无法保证程序是符合逻辑的,而语义分析的主要任务是针对结构上正确的源程序进行上下文有关性质的审查。

3.1标注检查

标注检查步骤检查的内容包括诸如变量使用前是否已经声明、变量与赋值之间的数据类型是否能够匹配等。在标注检查步骤还有一个重要的动作就是常量折叠。

int a=1+2;
在语法树上依然能够看到字面量“1”,“2”以及操作符“+”;但是经过常量折叠之后,它会被折叠为自变量“3”,所以在代码定义“int a=1+2”,比起直接定义“int a=3”并不会增加程序运行期哪怕一个CPU指令的运算量。

3.2数据以及控制流分析

数据以及控制流分析是对程序上下文逻辑更进一步的验证,它可以检查出局部变量在使用前是否已经赋值,方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理等问题,编译期间的数据以及控制流分析与类加载时的数据以及控制流分析目的基本是一致的,但是检验范围有所区别。

3.3解语法糖

语法糖,也称糖衣语法,是一个术语指在计算中语言中添加某种语法,这种语法对计算机的功能并没有影响,但是更方便程序员使用,简单的
说,使用语法糖可以增加程序的可读性,从而减少代码出错的机会。

Java中最常用的语法糖主要有:泛型,变长参数,自动装箱|拆箱等,虚拟机运行时不支持这些语法,他们在编译阶段还原为基础的语法结构,这个过程称为解语法糖!

3.4字节码生成

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

例如:之前提到的实例构造器 <init>()方法和类构造器<clinit>就是在这个阶段添加到语法树之中的,两个构造器的产生过程其实就是一个代码收敛的过程,编译器会把语句块,对于实例构造器是{}块,对于类构造器是static{}块,变量初始化,调用父类实例构造器等操作收敛到 <init>和<clinit>方法之中,并且保证是先执行父类的实例构造器,然后初始化变量最后执行语句块的顺序进行。

在完成了对语法树的遍历和调整之后,就会把填充了所有信息的符号表交给ClassWriter类,由这个类的writeClass方法输出 字节码,最终生成class文件,到此为止Javac的整个编译过程宣告结束。