JVM系列实践:前端编译优化

作者:陆金龙    发表时间:2022-12-23 23:15   

关键词:早期编译  前端编译  javac  ECJ  类型擦除  

主要内容来自:周志明著,《深入理解Java虚拟机》

1.编译器

前端编译器:Sun的Javac、Eclipse JDT的增量式编译器(ECJ)。

JIT编译器:HotSpot VM 的C1、C2编译器。

AOT编译器:GCJ(GNU Compiler for the Java)、Excelsior JET。

2.Javac编译器

Javac编译器是由Java语言编写的程序。放在JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac中。

除了JDK自身的API,只引入了JDK_SRC_HOME/langtools/src/share/classes/com/sun/*里面的代码。

Javac的编译过程大致分为3步:解析与填充符号表、注解处理、语义分析与字节码生成。

2.1 解析与填充符号表

(1)词法、语法分析

    词法分析:单个字符是程序编写过程的最小元素。词法分析是将源代码的字符流转变为标记(Token)集合,标记是编译过程的最小元素,包括关键字、变量名、运算符、字面量(由字母,数字等构成的字符串或者数值,它只能作为右值出现)等。

    词法分析由com.sun.tools.javac.parser.Scanner类实现。

    语法分析:根据Token序列构造抽象语法树(AST,Abstract Syntax Tree)的过程。语法树的每个节点都代表程序代码中的一个语法结构,例如包、类型、接口、修饰符、运算符、返回值及代码注释等。

    语法分析由com.sun.tools.javac.parser.Parser类实现。

(2)填充符号表

    符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,可以想象成哈希表中的K-V对。

    符号表登记的内容将用于语义检查和产生中间代码,符号表作为在目标代码生成阶段对符号名分配地址的依据。

    填充符号表由com.sun.tools.javac.comp.Enter类实现。

2.2 注解处理

    注解处理器:可以看做是一组编译器的插件,可以读取、修改、添加抽象语法树中的任意元素。

    这些插件可在处理注解期间对语法树进行修改。修改后,编译器将回到解析及填充符号表的过程重新处理。

    插入式注解处理器的初始化在initProcessAnnotations()方法中完成,执行过程在processAnnotations()方法中完成。

2.3 语义分析与字节码生成

  (1)语义分析

    语义分析过程分为标注检查、数据及控制流分析两个步骤。

    标注检查:内容包括变量使用前是否已被声明、变量与赋值的数据类型是否匹配、常量折叠等。

    标注检查由com.sun.tools.javac.comp.Attr类和com.sun.tools.javac.comp.Check类实现。

    数据与控制流分析:检查出程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理等问题。

    解语法糖:Java中最常用的语法糖(Syntactic Sugar)有泛型、变长参数、自动装箱/拆箱等。虚拟机不支持这些语法,他们在编译阶段被还原回简单的基础语法结构(这个过程即解语法糖)。

    Java中的泛型实现方法为类型擦除,是一种伪泛型,在编译后的字节码中被替换成了原来的原生类型。

    自动装箱/拆箱尽量避免在循环体中发生。

    解语法糖由desugar方法触发,在com.sun.tools.javac.comp.TransType和com.sun.tools.javac.comp.Lower类中完成。

  (2)字节码生成

     字节码生成把前面各个步骤生成的信息(语法树、符号表)转化为字节码写入到磁盘中,还进行少量代码的添加和转换工作。实例构造器<init>和类构造器<clinit>就是在这个阶段添加到语法树中的。转换工作例如把字符串的加操作替换成StringBuffer或StringBuilder的append()操作。

     字节码生成由com.sun.tools.javac.jvm.Gen类来完成。