10.2 Javac编译器

分析源码是了解一项技术的实现内幕最有效的手段,Javac编译器不像HotSpot虚拟机那样使用C++语言(包含少量C语言)实现,它本身就是一个由Java语言编写的程序,这为纯Java的程序员了解它的编译过程带来了很大的便利。

10.2.1 Javac的源码与调试

Javac的源码存放在JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/javac中[1],除了JDK自身的API外,就只引用了JDK_SRC_HOME/langtools/src/share/classes/com/sun/*里面的代码,调试环境建立起来简单方便,因为基本上不需要处理依赖关系。

以Eclipse IDE环境为例,先建立一个名为“Compiler_javac”的Java工程,然后把JDK_SRC_HOME/langtools/src/share/classes/com/sun/*目录下的源文件全部复制到工程的源码目录中,如图10-1所示。

figure_0322_0136

图 10-1 Eclipse中的Javac工程

导入代码期间,源码文件“AnnotationProxy Maker.java”可能会提示“Access Restriction”,被Eclipse拒绝编译,如图10-2所示。

figure_0322_0137

图 10-2 AnnotationProxyMaker被拒绝编译

这是由于Eclipse的JRE System Library中默认包含了一系列的代码访问规则(Access Rules),如果代码中引用了这些访问规则所禁止引用的类,就会提示这个错误。可以通过添加一条允许访问JAR包中所有类的访问规则来解决这个问题,如图10-3所示。

figure_0323_0138

图 10-3 设置访问规则

导入了Javac的源码后,就可以运行com.sun.tools.javac.Main的main()方法来执行编译了,与命令行中使用Javac的命令没有什么区别,编译的文件与参数在Eclipse的“Debug Configurations”面板中的“Arguments”页签中指定。

虚拟机规范严格定义了Class文件的格式,但是《Java虚拟机规范(第2版)》中,虽然有专门的一章“Compiling for the Java Virtual Machine”,但都是以举例的形式描述,并没有对如何把Java源码文件转变为Class文件的编译过程进行十分严格的定义,这导致Class文件编译在某种程度上是与具体JDK实现相关的,在一些极端情况,可能出现一段代码Javac编译器可以编译,但是ECJ编译器就不可以编译的问题(10.3.1节中将会给出这样的例子)。从Sun Javac的代码来看,编译过程大致可以分为3个过程,分别是:

解析与填充符号表过程。

插入式注解处理器的注解处理过程。

分析与字节码生成过程。

这3个步骤之间的关系与交互顺序如图10-4所示。

figure_0324_0139

图 10-4 Javac的编译过程[2]

Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中,其中主体代码如图10-5所示,整个编译最关键的处理就由图中标注的8个方法来完成,下面我们具体看一下这8个方法实现了什么功能。

figure_0324_0140

图 10-5 Javac编译过程的主体代码

[1]关于如何获取OpenJDK源码,请参考本书第1章。

[2]图片来源:http://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html,本书对相关术语进行了翻译。