本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前端编译与优化
- 前端编译器
- 把*.java文件转变成*.class文件的过程
- JDK的Javac、EclipseJDT中的增量式编译器(ECJ)
- Java虚拟机的即时编译器(常称JIT编译器,JustInTimeCompiler)运
- 行期把字节码转变成本地机器码的过程
- HotSpot虚拟机的C1、C2编译器,Graal编译器
- 静态的提前编译器(常称AOT编译器,AheadOfTimeCompiler)
- 直接把程序编译成与目标机器指令集相关的二进制代码的过程
- JDK的Jaotc、GNUCompilerfortheJava(GCJ)、ExcelsiorJET
Javac编译器
- 由Java语言实现
Java编译过程
从Javac代码的总体结构来看,编译过程大致可以分为1个准备过程和3个处理过程
- 准备过程:初始化插入式注解处理器
- 解析与填充符号表过程
- 词法、语法分析。将源代码的字符流转变为标记集合,构造出抽象语法树
- 填充符号表。产生符号地址和符号信息
- 插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段
- 分析与字节码生成过程
- 标注检查。对语法的静态信息进行检查
- 数据流及控制流分析。对程序动态运行过程进行检查
- 解语法糖。将简化代码编写的语法糖还原为原有的形式
- 字节码生成。将前面各个步骤所生成的信息转化成字节码
上述3个处理过程里,执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号
解析与填充符号表
解析过程包括了经典程序编译原理中的词法分析和语法分析两个步骤
词法、语法分析
经过词法和语法分析生成语法树以后,编译器就不会再对源码字符流进行操作了,后续的操作都建立在抽象语法树之上
- 词法分析
- 词法分析是将源代码的字符流转变为标记(Token)集合的过程
- 单个字符是程序编写时的最小元素,但标记才是编译时的最小元素
- 关键字、变量名、字面量、运算符都可以作为标记
- 词法分析过程由com.sun.tools.javac.parser.Scanner类来实现
“int a = b + 2”这句代码中就包含了6个标记,分别是int、a、=、b、+、2,虽然关键字int由3个字符构成,但是它只是一个独立的标记,不可以再拆分
- 语法分析
- 语法分析是根据标记序列构造抽象语法树的过程
- 抽象语法树(AbstractSyntaxTree,AST)是一种用来描述程序代码语法结构的树形表示方式
- 抽象语法树的每一个节点都代表着程序代码中的一个语法结构(SyntaxConstruct)
- 在Javac的源码中,语法分析过程由com.sun.tools.javac.parser.Parser类实现
- 这个阶段产出的抽象语法树是以com.sun.tools.javac.tree.JCTree类表示的
例如包、类型、修饰符、运算符、接口、返回值甚至连代码注释等都可以是一种特定的语法结构
填充符号表
- 符号表(SymbolTable)是由一组符号地址和符号信息构成的数据结构
- 符号表中所登记的信息在编译的不同阶段都要被用到
譬如在语义分析的过程中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的声明是否一致)和产生中间代码,在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的直接依据
- 在Javac源代码中,填充符号表的过程由com.sun.tools.javac.comp.Enter类实现
- 产出物是一个待处理列表,其中包含了每一个编译单元的抽象语法树的顶级节点,以及package-info.java(如果存在的话)的顶级节点
注解处理器
- 我们可以把插入式注解处理器看作是一组编译器的插件
- 当这些插件工作时,允许读取、修改、添加抽象语法树中的任意元素
- 如果这些插件在处理注解期间对语法树进行过修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止
- 每一次循环过程称为一个轮次(Round)
语义分析与字节码生成
- 经过语法分析之后,编译器获得了程序代码的抽象语法树表示
- 抽象语法树能够表示一个结构正确的源程序,但无法保证源程序的语义是符合逻辑的
- 语义分析的主要任务则是对结构上正确的源程序进行上下文相关性质的检查,譬如进行类型检查、控制流检查、数据流检查等等
过程
- 标注检查
- 数据及控制流分析
- 解语法糖
- 字节码生成
Java语法糖的味道
- 除了泛型、自动装箱、自动拆箱、遍历循环、变长参数和条件编译之外,Java语言还有不少其他的语法糖,如内部类、枚举类、断言语句、数值字面量、对枚举和字符串的switch支持、try语句中定义和关闭资源(这3个从JDK7开始支持)、Lambda表达式(从JDK8开始支持,Lambda不能算是单纯的语法糖,但在前端编译器中做了大量的转换工作)
泛型
- 泛型的本质是参数化类型(ParameterizedType)或者参数化多态(ParametricPolymorphism)的应用
- 即可以将操作的数据类型指定为方法签名中的一种特殊参数,这种参数类型能够用在类、接口和方法的创建中,分别构成泛型类、泛型接口和泛型方法。
- Java选择的泛型实现方式叫作“类型擦除式泛型”
- Java语言中的泛型只在程序源码中存在,在编译后的字节码文件中,全部泛型都被替换为原来的裸类型(RawType)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>其实是同一个类型