Java编译概览

原文:http://openjdk.java.net/groups/compiler/doc/compilation-overview/index.html


概述

将源文件编译成类文件并不是一个简单的过程,通常可以分成三个阶段。这个过程遵循按需处理的原则,因此不同源文件处理的速度不尽相同。

这个过程JavaCompiler来完成。

  1. 由命令行参数所指定的所有源文件会被parse成语法树,所有外部可见的定义都会enter到编译器的符号表。
  2. 调用所有适当的注解处理器。如果注解处理器产生了新的源文件或类文件,编译过程将重新开始,直到没有新的文件产生。
  3. 最后,解析器生成的语法树会被分析转换成类文件。在分析的过程中,可能会发现其他的被引用到的类(译者注:不是由命令行参数指定的类),编译器会检查源文件跟类文件路径,如果发现了这些类的源文件,那么这些文件也会被编译,但是不会对它们进行注解处理。

Parse 和 Enter

Scanner(译者注:词法分析器实现)将源文件进行Unicode转义处理,并转换成token流

token流被Parser读取,然后通过TreeMaker生成语法树。语法树由JCTree 的子类组成,这些子类实现了com.sun.source.Tree接口和它的子接口。

每一棵语法树都被交给Enter处理,Enter会将所有定义(译者注:成员变量,方法等)写入符号表。这个工作必须在分析语法树之前完成,因为语法树分析可能会引用到这些符号。这个阶段的输出是一个ToDo列表,其中包含了所有需要被分析并生成类文件的语法树。

Enter包括许多阶段,类通过队列从一个阶段进入另一个阶段。

class enter	→		Enter.uncompleted		→	MemberEnter (1)
			→	MemberEnter.halfcompleted	→	MemberEnter (2)
			→			To Do				→	(Attribute and Generate)
  1. 第1阶段,所有的类符号都会被enter到它们的enclosing scope中(译者注:enclScope.enter(c)),包括递归向下访问到的所有内部类。类符号会被设置一个 MemberEnter 对象作为 completer
    此外,如果发现任何带有包注解的package-info.java文件,它的top level语法树也会被放进ToDo列表。
  2. 第2阶段,执行MemberEnter.complete()。这个阶段是按需执行的, 所有未完成的类最终都会通过uncompleted队列处理完成。执行的操作包括,
    (1) 确定一个类的参数,父类和接口。
    (2) 所有在类中定义的符号(译者注:成员变量,方法等)enter到它们的scope中,除了在第1阶段已经处理过的类符号。
    (2) 依赖于一个类及其父类和内部类都完成了(1),这就是为什么在(1)之后我们将类放入一个halfcompleted队列。只有对一个类及其父类和内部类都执行了(1),我们才能进入(2)。
  3. 当所有符号都enter到符号表,这些符号上的任何注解都将被分析和验证。

第1阶段将对所有语法树进行,而第2阶段则是按要求来的。只有第一次访问类时,类成员才会enter到符号表。这是通过对相应的语法树设置MemberEnter对象作为completer来实现的。

注解处理

这部分由JavacProcessingEnvironment来处理。

概念上讲注解处理(译者注:对应下图中的Annotation processing)是编译(译者注:对应下图中的Compilation)前的一个准备步骤。这个过程由一系列回合组成,每一个回合都要parse和enter源文件,并调用适当的注解处理器。首个回合结束之后,如果注解处理器产生了新的源文件或类文件,这些文件在最终的编译中是必需的,那么将进行下一个回合。最后,当所有回合执行结束,真正的编译就将开始了。

而实际上,注解处理器的调用,在源文件被解析并且确定文件中所包含的声明之前,可能是不清楚的。因此,为了避免当不需要执行注解处理时无谓的文件解析(译者注:这里说的应该是避免第一回合的parse和enter?),JavacProcessingEnvironment采用了“out of phase”这样的概念模型,这仍然满足了注解处理作为一个整体在编译之前进行这样的概念需要。

JavacProcessingEnvironment在源文件parse和enter之后被调用。它确定了所有需要被加载和调用的注解处理器(译者注:由option指定,NameProcessIterator,或者spi方式查找,ServiceIterator)。通常情况下,如果在整个编译过程中有任何的错误发生,编译过程将在下一个合适的点停下来。然而有一个例外,就是在Enter阶段发现符号的缺失。这是因为缺失的符号定义有可能是由注解处理器产生的。

如果注解处理器将要被执行,它们会由单独的类加载器加载并运行。

当注解处理器运行完之后,JavacProcessingEnvironment会确定还需不需要进行下一回合的注解处理。如果需要,它将创建一个新的JavaCompiler对象,读取所有新创建的源文件,并重用之前解析过的语法树。这些语法树都会被enter到这个新的JavaCompiler实例的符号表,如果需要将会调用注解处理器。这一步会一直重复直到不再需要新一轮的注解处理。

最后,JavacProcessingEnvironment将会返回一个JavaCompiler对象,用于剩余的编译过程。这个对象有可能是最初用于parse跟enter的那个实例,也有可能是JavacProcessingEnvironment创建的用于最后一轮编译的实例。

Analyse 和 Generate

一旦由命令行所指定的所有源文件被parse并enter到JavaCompiler的符号表,并且所有的注解处理都完成了,JavaCompiler就可以开始分析语法树了,这些解析后的语法树现在就是等着生成相应的类文件了。

在分析语法树的过程中,可能会遇到一些被引用的类,而这些类并没有显式的指定要进行编译。这时候编译器会依赖于编译选项,在源文件路径(译者注:OptionName#SOURCEPATH)和类文件路径(译者注:OptionName#CLASSPATH)搜索这些类的定义。如果在一个类文件中找到了这个定义,那么这个类文件将被读取以确定这个定义;如果是在一个源文件中找到的,那么这个源文件将会被parse和enter,并且放入ToDo列表。这一步是通过让JavaCompiler实现了ClassReader.SourceCompleter接口来完成的(译者注:原文貌似有误,写的是Attr.SourceCompleter)。

分析语法树并生成类文件的工作是通过一系列的Visitor处理ToDo列表来完成的。并不需要让这些Visitor同步的来处理所有源文件,实际上,内存问题会让这一切变得完全不能接受。唯一的要求是,ToDo列表中的每一项最终都必须被每一个Visitor处理过,除非由于产生错误而导致了编译提前终止。

Attr

使用Attr将top level的类“属性化”,“属性化“的意思就是说,语法树里面的名字,表达式还有其他节点都会被解析并和相应的类型符号关联。通过Attr或者Check都可能会检查到许多的语义错误。

Flow

如果没有产生任何错误,流分析会开始进行,通过Flow完成。流分析包括变量的definite assignment分析,检查是否有不可达的语句,因此流分析可能会产生一些附加的错误。

TransTypes

通过TransTypes将使用泛型的代码转换成没有泛型的代码。

Lower

使用Lower来处理语法糖,Lower会重写语法树,用等价的、简化了的语法树来替换某些特殊类型的子树。包括了嵌套类、内部类,类字面常量,断言,foreach循环等等。对于每一个被处理的类,Lower都会返回一个语法树列表,这个列表包含了被转换后的类,以及它的所有被转换后的嵌套类和内部类。

尽管Lower一般是处理top level的类,它也可以处理package-info.java的top level语法树的。这种情况下,Lower会自己创建一个类来持有包上面的任何注解。

Gen

方法的代码由Gen来生成,Gen生成了Code属性,Code属性包含了JVM执行方法所需要的字节码。如果这一步成功了,ClassWriter将会写出类文件。

当写出类文件之后,这个类的语法树中的大部分(译者注:例如常量池)以及生成的字节码已经不再需要了。为了节省内存,相应的引用将会被置为null,以便进行内存回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值