本文部分摘自《深入理解 Java 虚拟机第三版》

概述

前端编译器(也叫编译器的前端)中的前端是指把 Java 文件转变为 Class 字节码文件的过程,顾名思义,前端编译器就是完成这一部分编译工作的。

前端编译器对代码的运行效率几乎没有任何优化措施可言,Java 虚拟机设计团队选择把对性能的优化全部集中到运行期的即时编译器中,这样可以让那些不是由 Javac 产生的 Class 文件也能享受到编译器优化措施所带来的性能红利。

前端编译器的优化主要集中在提高程序员的编码效率上,例如语法糖就是靠前端编译器实现的,而非依赖字节码或者 Java 虚拟机的底层改进来进行。

Javac 编译器

Javac 编译器是使用 Java 语言编写的程序。从 Javac 代码的总体结构来看,编译过程大致可以分为一个准备过程和三个处理过程,分别如下:

  1. 准备过程:初始化插入式注解处理器
  2. 解析与填充符号表过程,包括:
    • 词法、语法分析,将源代码的字符流转变为标记集合,构造出抽象语法树
    • 填充符号表,产生符号地址和符号信息
  3. 插入式注解处理器的进行注解处理
  4. 分析与字节码生成过程,包括:
    • 标注检查,对语法的静态信息进行检查
    • 数据流及控制流分析,对程序动态过程进行检查
    • 解语法糖,将简化代码编写的语法糖还原为原有形式
    • 字节码生成,将前面各个步骤所生成的信息转化为字节码

上述的三个处理过程,执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回之前的解析、填充过程表的过程,重新处理这些新符号。因此,从总体来看,三者之间的关系与交互顺序如图:

解析与填充符号表

1. 词法、语法分析

词法分析是将源代码的字符流转变为标记(Token)集合的过程,单个字符是程序编写时的最小元素,而标记则是编译时的最小元素,例如 int a = b + 2 这句代码就包含了 6 个标记,分别是 int、a、=、b、+、2,虽然关键字 int 由 3 个字符构成,但它是一个独立的标记。

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

经过词法和语法分析生成语法树后,编译器就不会再对源码字符流进行操作了,后续的操作都建立在抽象语法树上。

2. 填充符号表

完成语法分析和词法分析之后,下一个阶段是对符号表进行填充的过程了。符号表(Symbol Table)由一组符号地址和符号信息构成的数据结构,可以类比成哈希表(不一定是哈希表实现)。符号表中所登记的内容在编译的不同阶段都会用到,譬如在语法分析的过程中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的声明是否一致)和产生中间代码,在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的直接依据。

注解处理器

JDK5 之后,Java 语言提供了对注解的支持,注解原本只在运行期发挥作用,但在 JDK6 中又提供了“插入式注解处理器的”的标准 API,可以提前至编译期对代码中的特定注解进行处理,从而影响前端编译器的工作过程。插入式注解处理器可以读取、修改、添加抽象语法树中的任意元素,如果语法树被修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止。

语义分析与字节码生成

经过之前的步骤后,编译器获得了程序代码的抽象语法树表示,抽象语法树能够表示一个结构正确的源程序,但无法保证源程序的语义是符合逻辑的。而语义分析的主要任务则是对结构上正确的源程序进行上下文相关性质的检查,譬如类型检查、控制流检查、数据流检查等等。我们编码时经常能在 IDE 中看到有红线标注的错误提示,其中绝大多数都是来源于语义分析阶段的检查结果。

1. 标注检查

标记检查步骤要检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能匹配等等。在标注检查中,还会顺便进行一个称为常量折叠(Constant Folding)的代码优化,如果我们写了如下 Java 代码:int a = 1 + 2,则在抽象语法树上仍然能看到字面量 1、2 和操作符 + 号,但经过常量折叠优化后,它们会被折叠为常量 3,因此在代码里定义 int a = 1 + 2 并不会比 a = 3 的效率来得更高。

2. 数据及控制流分析

数据流分析和控制流分析是对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理等。编译时期的数据及控制流分析于类加载时的数据及控制流分析的目的基本一致,但有一些校验项只有在编译器或运行期才能进行。

3. 解语法糖

语法糖(Syntactic Sugar)指的是在计算机语言中添加的某种语法,这种语法对语言的编译结果和功能并没有实际影响,但却能更方便程序员使用该语言。简单来说,使用语法糖能减少代码量、增加程序的可读性,从而减少程序代码出错的机会。Java 中常见的语法糖有泛型、变长参数、自动装箱拆箱等等

4. 字节码生成

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

版权声明:本文为Yee-Q原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/Yee-Q/p/14374826.html