java 只去掉_深入理解Java语言编译器之整体流程

上一篇我们说了怎么搭建单步调试的OpenJDK编译器的环境,现在我们开始来了看看整个javac的工作流程。主要的步骤如下图所示:

1a684d6c3cb6db192a03bf62f53b78ee.png

一共分为9个大处理模块,在这里先给他们做一个大致的介绍,之后会对每一个模块做详细的介绍。

Parse:

这个模块主要是把.java文件解析成AST(Abstact syntax tree,抽象语法树),也就是龙书等经典著作里的词法分析和语法分析阶段。解析完成的结果是每一个java文件(包括module.java和package-info.java)生产一个JCTree.JCCompilationUnit类,所以这阶段产生的结果是List<JCCompilationUnit>。这个阶段只管解析文件格式对的java文件,不会对里面java的主义进行解析,所以下面这样的文件也能正确解析,因为它都符合Java语言的词法,这些问题都是在后面的阶段发现并报告。

/**
 * 文件全路径与名字 icu/mianshi/openjdk/compiler/InvalidJavaFile.java
 */
package icu.mianshi.openjdk.compiler;

/**
 * 1. public类名与文件名不一致
 */
public class AnotherInvalidJavaFile {
    public static void main(String[] args) {
        System.out.println("InvalidJavaFile");
    }

    /**
     * 2. 重复的Java方法定义
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("InvalidJavaFile");
    }
}

/**
 * 3. InnerClass与DerivedClass循环引用。
 */
class InnerClass extends  DerivedClass{}

/**
 * 4. 重复的类定义。
 */
class InnerClass{}

class DerivedClass extends InnerClass {

}

Module

这个阶段就做一件事,把整个module的module-info.java加载、解析、并把相应的信息增加到AST上去。这个信息只存在JCCompilationUnit中。

Enter

这个阶段做的事件主要分两大部分:

a. 把AST里所有的class symbol(这里的class symbol不仅仅指狭义上的class,还包括interface, enum, record)放到他们的enclosing scope里去。另外如果package-info.java里如果有Annotation的话,也是要作同样的处理。

enclosing scope:是指一个Java语言对象依附的对象,比如成员变量和方法的enclose scope就是它所在的类。
临时变量的enclosing scope就是它所在的方法。
所有顶层class symbol的enclosing scope就是它所有的包。

b. 完成上一个阶段了,就会把上一个阶段所有类引用到的其它class symbol加载上来。这里面包括jdk里引用到的类 java.lang.*或是java.util.*或是第三方库里的class文件。还有一个重要的是如果这时候用到了基本类还在java源文件里,也需要编译然后加载进来。

Annotate

这个阶段是我们可以通过代码来影响Java编译的阶段,就是继承一个AbstractProcessor来处理你自己增加在代码里的Annotation,具体例子可以看这里。基功能由JavacProcessingEnvironment来负责处理的,这是编译前的预备步骤,会进行多轮,每一轮把需要的文件编译和装载,直到没有新的文件需要编译和装载为止。

Annotation processors(就是你扩展的AbstractProcessor)是通过单独的ClassLoader来加载的。因为它不能参与后面阶段的编译,所以当它处理完成后需要从JVM卸载。

当一轮Annotation processor跑完之后,JavacProcessingEnvironment确定是否有新的Annotation processor要跑,如果有,则重新创建一个JavaComplier并重用之前的已经解析好的AST,然后所有的符号都装载到新的JavaComplier中的SymTab中。当所有的步骤完成之后,JavacProcessingEnvironment返回一个JavaComplier的对象,这个JavaComplier对象就是我们接下使用的编译对象。这样我们就获得好经过处理好的AST,不会再有外部的变化了。

Attribute

到了这里,就到了语义分析的阶段了。

在这里,所有TopLevel的类(就是放在AST里的类和它引用的类)的类名、表达式和其它的元素都已经正解析了并且对找到了对应的类型和符号了。大多数的语义错误(sematic errors)都是在这个阶段被Attr或是Check发现的。其中Check是Attr的成员类,主要做各种静态检查。

Flow

主要是分析负值语句和不可到达的语句的。然后把它们去掉。

TransTypes

这里把泛型去掉。大家都知道Java其实是支持的假泛型,所以在class文件和JVM层面根本不知道这玩意,就是在这阶段处理掉的。

Lower

这个阶段把Java的语法糖去掉,这个阶段做重写syntax tree,把一些只有在Java语法上支持但是JVM不支持的语法去掉,用比较简单的语法树换掉,最著名的是(switch(String))这个语法,比较全的列表在这里。同时会处理静态嵌套类(Static Nested Class)和内部类(Inner Class), 类字面常量(class literals),断言,foreach 环行。每个类的处理结果包括该类的和静态嵌套类(Static Nested Class)和内部类(Inner Class)的语法树。

一般说来,Lower只处理顶层的class,但也会处理package-info.java,不过处理方式是创建一个合成类,这个类包括这个包的所有Annotation。

Generate

这是Java编译器的最后一个阶段,到了这里,所有的材料都准备好了,各个类的常量、方法里的代码都会生成JVM的指令,如果没有什么问题,那么就直接由ClassWriter写到文件里就结束了。

总结:

最后,其实Java编译器按龙书说的,应该是一半吊子的编译器,只到中间代码生成这部分就完成了。 JVM的指令集可以算是一中间语言。所以JVM的指令也可以编译成Native 机器码或是像Android的Dalvik虚拟机一样生成它自己的代码。

我目前一个计划就是写完Java编译器这部分了,再研究一下JIT。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值