欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Javac原理剖析

程序员文章站 2022-05-19 16:09:21
...

Javac原理剖析

Javac是什么?

​ Javac是一种编译器,能将一种语言规范转化为另一种语言规范。Javac的任务就是将Java源代码语言转化成JVM能够识别的一种语言,然后由JVM将JVM语言转化成当前这个机器能够识别的机器语言。

Javac原理剖析

​ Javac的任务就是将Java源码成Java字节码,也就是JVM能够识别二进制码。从表面上看就是上面的部分将.java文件转成.class文件,而实际上是将Java的源代码转化成一连串二进制数字,这些二进制数字是有格式的,只有JVM能够正确识别他们到底表达了什么意思。

Javac编译器的基本结构

​ Javac的作用是将符合规范源代码转化成为字节码,需要哪些过程呢?我们可以复习一下大学时候的编译原理知识。

Javac原理剖析

​ 如何编译程序呢?

​ 1.词法分析:主要是读取源代码,一个字节为一节地读取进来,找些那些是语言的关键字例如,if、else等。词法分析就是从源代码中找出一些规范化的Token流,一个形象的比如就是人类能分清楚一个句子的单词和标点符号。

​ 2.语法分析:检查这些关键词组合在一起是不是符合Java规范,如在if后面是不是跟着布尔表达式,就像人类能分清楚一个句子的主语谓语宾语,检查是否符合语法。语法分析的结果就是形成一个Java语言规范的抽象语法树。

​ 3.语义分析:将一些复杂难懂的语法转化为简单易懂的语法。这个过程对应于将复杂难懂的文言文转化为白话文。语义分析的结果是将复杂难懂的语法转化为最简单的语法,对应于Java,可以将注解等转化为抽象语法树。

​ 最后是代码生成将抽象语法树生成字节码,可以对应于人类的将中文转化为英文组合成新的句子。

Javac原理分析

词法分析器

我们可以通过一个简单的Java类来进行词法分析。

package compile;

public class Cifa {
    int a;
    int b=a;
    int c=a+1;

}

​ 我们可以调用com.sun.tools.javac.main.Main类来实现手动编译指定的类。

Javac原理剖析

​ Javac的主要词法分析器的结构类是com.sun.tools.javac.parser.Lexer,这个默认实现类是com.sun.tools.javac.parser.Scanner,这个类会逐步读取Java源程序的单个字符,然后解析出符合Java语言规范的Token序列。所设计的类如下图

Javac原理剖析

​ 这两个Factory生成两个接口类的Scanner和JavacParser,JavacParser规定哪些词是符合Java语言规范规定,而具体读取和归类不同的词法操作由Scanner完成,Token规定了完成Java语言的合法关健词,Names用来存储和表示解析后的词法。

​ 词法分析额过程是在JavacParser的parseCompilationUnit()方法中完成,主要代码如下:

Javac原理剖析

Javac原理剖析

Javac原理剖析

​ 这段代码表示从源文件的一个字符开始,按照Java语法规范依次出现packege、import、类定义,以及属性和方法定义等,最后构建一个抽象的语法树。

​ 词法分析器的分析结构就是将这个列中的所有关键词匹配到Token类中的所有项中的任何一项。上述代码分析的Token流是:

Javac原理剖析

​ 这里有两个关键点是:Javac是如何分辨这一个个Token的呢?还有就是Javac如何知道啊哪些字符组合在一起就是一个Token的呢?

​ 第一个问题的答案是这样的:Javac在进行词法分析时会由javacParser根据Java语言规范来控制什么顺序、什么地方应该出现什么Token。

​ 判断当前的Token是不是Token.PACKAGE,使用qualident方法。这个方法的源代码是:

Javac原理剖析

上述代码的执行流程如下:

Javac原理剖析

通过上图,我们明白了Token的顺序规则,之后我们来解决下一个问题,如何判断哪些字符组合是一个Token 的规则是在Scanner的nextToken方法中定义,没调用一次这个方法会构成一个Token,而这个Token必须是com.sun.tools.javac.parser.Token中的任何元素之一。

那么如何将读取每个token转化呢?这个任务是在com.sun.tools.javac.parser.Keywords类中完成,Keywords负责将所有字符集合对应到Token集合中。

字符集合到Token转换相关的类关系如下图
Javac原理剖析

每个字符集合都会是一个Name对象,所有的Name对象都存储在Name.Table这个内部类中,这个类也就是对应的这类的符号表。而Keywords会将Token的对应关系,这个关系保存在Keywords类的key数组中,这个key数组只保存了在com.sun.tools.javac.parser.Token类中定义的所有Token到Name对象的关系,而其他的所有字符集合Keywords都会将它对应到Token.IDENTIFIER类型。

字符集合转化成Name对象,Name对象对应到Token的转换关系如下图;

Javac原理剖析

语法分析器

​ 语法分析器是将词法分析器分析的Token流建成更加结构化的语法树,也就是将一个个单词组装成一句话,一个完整的语句。

​ Javac的语法树使得Java源码更加结构化,这种结构化可以为后面进一步处理提供方便。每个语法书上的节点都是com.sun.tools.javac.tree.JCTree的一个实例。

​ 关于语法规则是:

​ 1.每一个语法节点都会实现一个接口xxxTree,这个接口继承自com.sun.source.tree.Tree接口。

​ 2.每个语法节点都是com.sun.tools.javac.tree.JCTree的子类,并且会实现第一节点中的xxxTree接口类,这个类的名称类似于JCxxx。

​ 3.所有的JCxxx类都作为一个静态内部类定义在JCTree类中。

Javac原理剖析

JCTree类中有如下三个重要的属性项:

​ 1.Tree tag:每个语法节点都会用一个整形常数表示,并且每个节点类型的数值是在其哪一个的基础上加1,顶层节点TOPLEVEL是1,而IMPORT节点等于TOPLEVEL加1,等于2.

​ 2.pos:也是一个整数,它存储的是这个语法节点在源代码中的起始位置,一个文件的位置是0,而-1表示不存在。

​ 3.type:它表示的是这个节点是什么Java类型,如是int、float还是String。

​ 在package的词法分析的过程是

Javac原理剖析

这行代码会调用TreeMaker类,根据Name对象构建成一个JCIdent语法节点,如果多几目录,将构建成JCFieldAccess语法节点,JCFieldAccess节点可以是嵌套关系。

​ 下面是import语法树的构造代码:

Javac原理剖析

Javac原理剖析

​ 整个JCImport节点的语法树如下:
Javac原理剖析

​ Import节点解析完成之后就是类的鸡西,类包含interface、class、enum,下面是以calss为例来介绍class是如何解析成一颗语法树的。

Javac原理剖析

Javac原理剖析

​ 第一个Token是Token.CLASS这个累的关键词,接下来是一个用户自定义的Token.IDENTIFIER,这个Token是类名。

​ 最后解析整个classBody解析的结果保存在list集合中,最后将会把这些子节点添加到JCClassDeca这颗class树中。

​ 下面的代码为例;

Javac原理剖析

​ 这段代码对应的语法树如下:

Javac原理剖析

​ 上面的语法树去掉了一些节点类型。在将这个类解析完成之后,会将这个类节点添加到这个类对应的包路径的顶层节点中,这个顶层节点是JCCompilationUnit.JCCompilationUnit持有以package作为pid和JCClassDec1的集合,这个整个java文件被解析完成,这颗完整的语法树如下:

Javac原理剖析

​ 关于语法分析的一点需要说明的是,所有语法节点的生成树都是在TreeMaker类中完成的,TreeMaker实现了在JCTree.Factory接口中定义的所有节点的构成方法。

语义分析器

​ 在语法书的基础上进一步处理,例如给类添加默认的构造器函数,检查变量在使用前是否已经初始化,将一些常量进行合并处理,检查操作变量类型是否匹配,检查所有的操作数据是否可达,检查checked exception异常是否已经捕获或者破除,解除Java语法糖等。

​ 将在Java类中的符号输入到符号表中主要由com.sun.tools.javac.comp.Enter类来完成,这个类主要完成以下两个步骤:

​ 1.将所有类中出现的符号输入到类自身的符号表中,所有类符号、类的参数类型符号、超类符号和继承的接口类型符号等都存储到一个未处理的列表中。

​ 2.将这个未处理列表中所有的类都解析到各自的类符号列表中,这个操作是在MemberEnter.complete()中完成。

​ 在上面的Yufa类中,会添加一个无参构造函数。

​ 接下来使用com.sun.tools.javac.comp.Attr检查语法的合法性进行逻辑判断:

​ 1.变量的类型是否匹配

​ 2.变量在使用前是狗已经初始化

​ 3.能够推导出泛型方法的参数类型

​ 4.字符串常量的合并

​ 例如:

Javac原理剖析

​ 经过解析后,源代码变成:

Javac原理剖析

​ 这里将两个字符串合并成一个字符串。

​ 在标注完成以后,由com.sun.tools.javac.comp.Flow完成数据流分析,主要完成的工作如下:

1.检查变量的使用前时候已经被正确赋值,除了Java的原始类型,其他像String类型和对象的引用都必须在使用前先赋值

2.保证final修饰的变量不会被重复复制。

3.要确定方法的返回值类型

4.所有的CheckedException都要捕获或者向上抛出。

5.所有的语句都要被执行到。

​ 语义分析的足后一个步骤是执行com.sun.tools.javac.comp.Flow,这就是在进一步对语法树进行语义分析,如消除一些无用的代码;去除永不真的条件判断;解除一些语法糖,类型的自动转化操作。

一些例子如下:

​ 类型转化:

Javac原理剖析

​ for循环解析:

Javac原理剖析

​ 内部类:

Javac原理剖析

Javac原理剖析

微信公众号

有兴趣的同学可以关注小编哟!
Javac原理剖析