概述
Java语言的“编译期”其实是一段“不确定”的操作过程,因为他可能是指一个前端编译器把java文件转变成class文件的过程;也可能是指虚拟机的后端运行期编译器把字节码转变成机器码的过程;还可能是指使用静态提前编译器直接把java文件编译成本地机器代码的过程。以下是这三类比较有代表性的编译器:
前端编译器:Sun的javac、Eclipse JDT中的增量式编译器。
JIT编译器:HotSpot VM的C1、C2编译器。
AOT编译器:GNU Compiler for the Java (GCJ)、Excelsior JET。
Javac这类编译器对代码的运行效率几乎没有任何优化措施。虚拟机设计团队把对性能的优化集中到了后端的即时编译器中,这样可以让那些不是由Javac产生的Class文件(如JRuby、Groovy等语言的Class文件)也同样能享受到编译器优化所带来的好处。但是,相当多新生的Java语法特性,都是靠编译器的“语法糖”来实现,而不是依赖虚拟机的底层改进来支持,可以说,Java中即时编译器在运行期的优化过程对于程序运行来说更重要,而前端编译器在编译器的优化过程对于程序编码来说关系更加密切。
Javac编译器
Javac本身就是一个由Java语言编写的程序,这为纯Java的程序员了解它的编译过程带来了很大的便利。
Javac的源码与调试
从Sun Javac的代码来看,编译过程大致可以分为3个过程,分别是:
解析与填充符号表过程。
插入式注解处理器的注解处理过程。
分析与字节码生成过程。
解析与填充符号表
解析步骤包括了经典程序编译原理中的词法分析和语法分析两个过程。
1.词法、语法分析
词法分析是将源代码的字符流转变为标记集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素。在Javac的源码中,词法分析过程由com.sun.tools.javac.parser.Scanner类来实现。
语法分析是根据Token序列构造抽象语法树的过程,抽象语法树是一种用来描述程序代码与法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构。语法分析过程由com.sun.tools.javac.parser.Parser类实现。
2.填充符号表
完成了语法分析和词法分析之后,下一步就是填充符号表的过程。符号表中所登记的信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。在Javac源代码中,填充符号表的过程由com.sun.tools.javac.comp.Enter类实现。
注解处理器
在JDK1.5之后,Java语言提供了对注解的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。
语义分析与字节码生成
语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,如进行类型审查。
1.标注检查
语义分析过程分为标注检查以及数据及控制流分析两个步骤。标注检查步骤检查的内容包括诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。
2.数据及控制流分析
数据及控制流分析是对程序上下文逻辑更进一步的验证,他可以检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理了等问题。将局部变量声明为final,对运行期是没有影响的,变量的不变性仅仅由编译器在编译期间保障。
3.解语法糖
语法糖也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说,使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
Java中最常用的语法糖主要是前面提到过的泛型、变长参数、自动装箱、拆箱等,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础与法结构,这个过程称为解语法糖。
4.字节码生成
字节码生成是Javac编译过程的最后一个阶段,在Javac源码里面由com.sum.tools.javac.jvm.Gen类来完成。字节码生成极端不仅仅是把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。
Java语法糖的味道
几乎各种语言或多或少都提供过一些语法糖来方便程序员的代码开发,这些语法糖虽然不会提供实质性的功能改进,但是它们或能够提高效率,或能提醒语法的严谨性,或能减少编码出错的机会。
泛型与类型擦除
自动装箱、拆箱与遍历循环
条件编译