Javac编译器
Java技术下的编译期在不同的语境下会有不同的编译器实现产品:
- 1、前端编译器:jdk的javac、Eclipse JDT中的增量式编译器
- 2、即时编译器:hotspot虚拟机中的c1、c2编译器,graal编译器。
- 3、提前编译器:jdk的jaotc、gcj。
这里讨论javac编译器。
Javac编译过程大致可以分为1个准备过程和3个处理过程:
- 1、准备过程:初始化插入式注解处理器。
- 2、解析与填充符号表过程:
- 词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树,
- 填充符号表。产生符号地址和符号信息。
- 3、插入式注解处理器的注解处理
- 4、分析与字节码生成过程,包括:
- 标注检查。对语法的静态信息进行检查。
- 数据流及控制流分析。对程序动态运行过程进行检查。
- 解语法糖。将简化代码编写的语法糖还原为原有的形式。
- 字节码生成。将前面各个步骤所生成的信息转化成字节码。
上面的处理过程中,执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号。
一、解析与填充符号表
解析过程包括了经典程序编译原理中的词法分析和语法分析俩个步骤
1、词法、语法分析
词法分析是将源代码的字符流转变为标记集合的过程,单个字符串是程序编写的最小元素,但标记才是编译时最小元素,关键字、变量名、字面量、运算符都可以作为标记。
语法分析是根据标记序列构造抽象语法树的过程,抽象语法树是一种用来描述程序代码语法结构的树形表示方式,抽象语法树的每一个节点都代表着程序代码中一个语法结构,例如包、类型、修饰符、接口、返回值等。
经过词法和语法分析生成语法树之后,编译器就不会再对源码字符进行操作了,后续的操作都建立在抽象语法树上。
2、填充符号表
符号表是由一组符号地址和符号信息构成的数据结构。符号表中所登记的信息在编译阶段都要被用到。譬如在语义分析过程中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的声明是否一致)和产生中间代码,在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的直接依据。
二、注解处理器
jdk5之后,Java语言提供了对注解的支持。注解的设计原本上是与普通的Java代码一样,都只会在程序运行期间发挥作用的,但在jdk6设计了一组插入式注解处理器的标准api,可以提前至编译期对代码中的特定注解进行处理,从而影响到前端编译器的工作过程。我们可以把插入式注解处理器看作是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环过程称为一个轮次。
三、语义分析与字节码生成
经过语法分析之后,编译器获得了抽象语法树。抽象语法树能够表示一个结构正确的源程序,但无法保证源程序的语义是符合逻辑的。
语义分析的主要任务则是对结构上正确的源程序进行上下文相关性的检查,譬如进行类型检查、控制流检查、数据流检查等
1、标注检查
标注检查步骤要检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配,等等。在标注检查中,还会顺便进行一个成为常量折叠的代码优化。
2、数据及控制流分析
数据流分析和控制流分析是对程序上下文逻辑更进一步的验证,他可以检查诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确的处理了等问题。
编译时的数据及控制流分析与类加载时数据及控制流分析的目的基本上可以看作是一致的,但是检验的范围会有所区别,有一些校验项只有在编译器或运行期才能进行。
3、解语法糖
语法糖指的是在计算机语言中添加的某种语法,这种语法对语言的编译结果和功能没有实际影响,但是却能更方便程序员使用该语言。通常来说语法糖能够减少代码量、增加程序的可读性,从而减少程序代码出错的机会。
Java中常见的语法糖包括泛型、变长参数、自动装箱拆箱等,Java虚拟机运行时并不直接支持这些语法,它们在编译阶段被还原回原始的基础语法结构。即解语法糖。
4、字节码生成
字节码生成是Javac编译过程的最后一个阶段。字节码生成阶段不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码指令写到磁盘中,编译器还进行了少了的代码添加和转换工作。例如实例构造器init方法和类构造器clinit方法,字符串的加操作使用stringbuilder或者stringbuffer替换等。