Android进阶——Javac编译解析

Javac编译器

1.Javac的源码与调试

Javac的源码下载地址:Javac的源码下载地址,在Myeclipse中新建项目Compiler_javac,把源码复制到项目中。

Javac的源码目录:

Android进阶——Javac编译解析

从Sun Javac的代码来看,编译进程大致能够分为3个进程:

Android进阶——Javac编译解析

Java编译动作的进口类为JavaCompiler,上述3个进程的代码逻辑会集在这个类的compile()和compile2()办法中。源代码如下:

Android进阶——Javac编译解析

2.解析与填充符号表

2.1词法剖析、语法剖析

词法剖析:是将源代码的字符流转变为符号(Token)调集,单个字符是程序编写进程的最小元素,而符号则是编译进程的最小元素,关键字、变量名、字面量、运算符都能够看成符号。

在Javac的源码中,词法剖析进程由com.sun.tools.javac.parser.Scanner类完结。

语法剖析:是依据Token序列结构笼统语法树的进程,笼统语法树(Abstract Syntax Tree,AST)是一种用来描绘程序代码语法结构的树形表明方法,语法树的每一个节点都代表着程序代码中的语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值乃至代码注释都能够是一个语法结构。

在Javac的源码中,语法剖析进程由com.sun.tools.javac.parser.Parser类完结。

这个阶段发生的笼统语法树由com.sun.tools.javac.tree.JCTree类表明,经过这个进程之后,编译器就根本不会再对源码文件操作了,后续的操作都建立在笼统语法树之上。

在Myeclipse中装置ASTView插件的下载地址:ASTView插件下载地址,装置插件后Window中Show View中挑选ASTView。

笼统语法树结构视图如下:

Android进阶——Javac编译解析

2.2填充符号表

符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,读者能够把它想象成哈希表中K-V值对的方法(实践上符号表纷歧定是哈希表完结,能够是有序符号表、树状符号表、栈结构符号表等)。

符号表中所挂号的信息在编译的不同阶段都要用到。

1)在语义剖析中:符号表所挂号的内容将用于语义查看(如查看一个姓名的运用和原先的说明是否共同)和发生中间代码。

2)在方针代码生成阶段:当对符号名进行地址分配时,符号表是地址分配的依据。

在Javac源码中,填充符号表的进程由com.sun.tools.javac.comp.Enter类完结,此进程的出口是一个待处理列表(To Do List),包含了每一个编译单元的笼统语法树的尖端节点,以及package-info.java(假如存在的话)的尖端节点。

3.注解处理器

JDK1.6提供了一组刺进式注解处理器的规范API在编译期间对注解进行处理,我们能够把它看做是一组编译器的插件,在这些插件里边,能够读取、修正、增加笼统语法树中的恣意元素。假如这些插件在处理注解期间对语法树进行了修正,编译器将回到解析及填充符号表的进程重新处理,直到一切刺进式注解处理器都没有再对语法树进行修正停止,每一次循环称为一个Round,也便是上面绿色图中回环的进程。

在Javac源码中,刺进式注解处理器的初始化进程是在initProcessAnnotations()办法中完结的,而它的履行进程则是在processAnnotations()办法中完结的,这个办法判断是否还有新的注解处理器需求履行,假如有的话,经过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()办法生成一个新的JavaCompiler对象对编译的后续进程进行处理。

4.语义剖析与字节码生成

语法树能表明一个结构正确的源程序的笼统,但无法确保源代码是符号逻辑的。而语义剖析的主要任务是对结构上正确的源程序进行上下文有关性质的审查,如进行类型审查。

语义剖析包含两个进程:

1)标示查看(attribute()办法)

2)数据及操控流剖析(flow()办法)

4.1符号查看

查看的内容包含:变量运用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。

常量折叠:

int a = 1+2;这个刺进式表达式(Infix Expression)的值现已在语法树上标示出来了(ConstantExpressValue:3)。

Android进阶——Javac编译解析

由于编译期间进行了常量折叠,所以在代码里边定义“a=1+2”比起直接定义“a=3”,并不会增加程序运行期哪怕只是一个CPU指令的运算量。

标示查看进程在Javac源码中的完结类是com.sun.tools.javac.comp.Attr类和com.sun.tools.javac.comp.Check类。

4.2数据及操控流剖析

数据及操控流剖析是对程序上下文逻辑更进一步的验证,它能够查看:程序局部变量在运用前是否有赋值、办法的每条途径是否都有返回值、是否一切的受查反常都被正确处理了等。

编译时期的数据及操控流剖析与类加载时的数据及操控流剖析的意图根本上是共同的,但校验范围有所区别,有一些校验项只要在编译期或运行期才干进行。

局部变量与字段(实例变量、类变量)是有区别的,它在常量池中没有CONSTANT_Fieldref_info的符号引证,天然就没有拜访标志(Access_Flags)的信息,乃至或许连称号都不会保存下来(取决于编译时的选项),天然在Class文件中不或许知道一个局部变量是不是声明为final了。因而,将局部变量声明为final,对运行期是没有影响的,变量的不变性只是由编译器在编译期间确保。

在Javac的源码中,数据及操控流剖析的进口是flow()办法,具体操作由com.sun.tools.javac.comp.Flow类完结。

4.3解语法糖

语法糖(Syntactic Sugar),也称糖衣语法。指在计算机言语中增加的某种语法,这种语法对言语的功用并没有影响,可是更便利程序员运用。

Java中最常用的语法糖:泛型、变长参数、主动装箱/拆箱等,虚拟机运行时不支持这些语法,它们在编译阶段复原回简单的根底语法结构,这个进程称为解语法糖。

在Javac的源码中,解语法糖的进程由desugar()办法触发,在com.sun.tools.javac.comp.TransTypes类和com.sun.tools.javac.comp.Lower类中完结。

4.4字节码生成

字节码生成是Javac编译进程的最终一个阶段,在Javac源码里边由com.sun.tools.javac.jvm.Gen类来完结。

字节码生成阶段不只是是把前面各个进程所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少数的代码增加和转化作业。

实例结构器()办法和类结构器()办法便是在这个阶段增加到语法树之中的(注意,这儿的实例结构器并不是指默认结构函数,假如用户代码中没有提供任何结构函数,那编译器将会增加一个没有参数的、拜访性(public、protected、private)与当前类共同的默认结构函数,这个作业在填充符号表阶段就现已完结),这两个结构器的发生进程实践上是一个代码收敛的进程,编译器会把句子块(对于实例结构器而言是“{}”块,对于类结构器而言是“static{}”块)、变量初始化(实例变量和类变量)、调用父类的实例结构器(只是是实例结构器,()办法中无须调用父类的()办法,虚拟机会主动确保父类结构器的履行,但在()办法中经常会生成调用java.lang.Object的()办法的代码)等操作收敛到()和办法之中,而且确保一定是按先履行父类的实例结构器,然后初始化变量,最终履行句子块的次序进行,上面所述的动作由Gen.normalizeDefs()办法来完结。

除了生成结构器以外,还有其它的一些代码替换作业用于优化程序的完结逻辑,如把字符串的加操作替换为StringBuffer或StringBuilder的append()操作等。

完结了对语法树的遍历和调整之后,就会把填充了一切所需信息的符号表交给com.tools.javac.jvm.ClassWriter类,由这个类的writeClass()办法输出字节码,生成最终的Class文件,到此停止整个编译进程宣告结束。

编译进程

javac 的编译进程能够分为 1 个准备进程和 3 个处理进程:

1 个准备进程: ◉ 准备进程:初始化刺进式注解处理器 3 个处理进程: ◉ 解析与填充符号表 ◉ 注解处理(刺进式注解器) ◉ 剖析与字节码生成

javac 的编译进程,如下图所示:

Android进阶——Javac编译解析

在上述的编译进程中,在 “处理注解” 的阶段或许会发生新的符号,那么就必须再履行一次 “解析与填充符号表” 处理新的符号。

剖析源码,javac 编译代码是 com.sun.tools.javac.main.JavaComplier 类的 compile() 和 compile2() 办法。

compile() 办法的部分代码:

// 1 初始化刺进式注解处理器initProcessAnnotations(processors);
    // These method calls must be chained to avoid memory leaksdelegateCompiler =    
    // 3 注解处理    processAnnotations(            
    // 2.2 解析与填充符号表--填充符号表            enterTrees(stopIfError(CompileState.PARSE,         // 2.1 解析与填充符号表--词法剖析、语法剖析            parseFiles(sourceFileObjects))),    classnames);
    // 4 剖析与字节码生成delegateCompiler.compile2();delegateCompiler.close();elapsed_msec = delegateCompiler.elapsed_msec;

compile2() 办法的部分代码:

case BY_TODO:    while (!todo.isEmpty())
    // 4.4 剖析与字节码生成--生成字节码        generate(            
    // 4.3 剖析与字节码生成--解语法糖            desugar(               
    // 4.2 剖析与字节码生成--数据流剖析                flow(                   
    // 4.1 剖析与字节码生成--标示                    attribute(todo.remove()))));    break;

解析与填充符号表

解析与填充符号表分为两个进程:词法、语法剖析 和 填充符号表。

具体流程为: 生成 Token 调集 –> 生成笼统语法树 –> 填充符号表

  • 词法、语法剖析

词法、语法剖析 parseFiles() 办法的源码,如下图所示:

Android进阶——Javac编译解析

词法剖析便是将源代码的字符流转变成符号(Token)调集的进程,符号是编译时的最小元素。

词法剖析的进程由 com.sun.tools.javac.parser.Scanner 类完结,逐一读取源代码的单个字符,经过 nextToken() 办法结构每一个 Token。

关键字、变量名、字面量、运算符都能够作为符号。

例如,下面这段代码,包含了 6 个符号,分别是:int、a、=、b、+、2。

int a = b + 2;

转变成符号(Token)调集的成果,如下图所示:

Android进阶——Javac编译解析

语法剖析便是依据 符号(Token)调集 结构成 笼统语法树 的进程,笼统语法树(AST)的每一个节点代表着程序代码的一个语法结构。

IntelliJ IDEA 能够装置 JDT AstView 插件,可视化源代码的笼统语法树。

JDT AstView plugins.jetbrains.com/plugin/9345…

上面代码:int a = b + 2; 的笼统语法树的结构视图,如下图所示:

Android进阶——Javac编译解析

语法剖析进程由 com.sun.tools.javac.parser.Parser 类完结,笼统语法树由 com.sun.tools.javac.tree.JCTree 类表明。

经过词法、语法剖析生成笼统语法树之后,编译器就不会再对源码字符流进行操作,后续的操作都是建立在笼统语法树之上。

  • 填充符号表

完结词法、语法剖析之后,下一个阶段便是填充符号表。

填充符号表 enterTrees() 办法的源码,如下图所示:

Android进阶——Javac编译解析

符号表(Symbol Table)是由一组符号地址和符号信息构成的数据结构,能够类比想象成哈希表中键值对的存储方法。

符号表所挂号的信息在编译的不同阶段都会被用到,在方针代码生成阶段,对符号名进行地址分配时,符号表是地址分配的直接依据。

填充符号表的进程由 com.sun.tools.javac.comp.Enter 类完结,将会生成一个待处理列表,其间包含了每一个编译单元的笼统语法树的尖端节点,以及 package-info.java(假如存在)的尖端节点。

package-info.java:为包级文档和包级别注释

注解处理

注解处理 processAnnotations() 办法的源码,如下图所示:

Android进阶——Javac编译解析

刺进式注解处理器是在编译器对代码中的特定注解进行处理,在前端编译器的作业进程中,对笼统语法树中的恣意元素进行读取、修正、增加。

刺进式注解处理器在处理注解的进程中,假如对语法树进行过修正,那么编译器将会重新到解析与填充符号表的阶段重新处理,直到没有对语法树进行修正停止,每一次循环进程称为一次轮次(Round)。

经过 com.sun.tools.javac.processing.JavacProcessingEnvironment 类的 domProcessing() 办法来履行语法树中的刺进式注解处理器,生成新的 JavaCompiler 对象。

剖析与字节码生成

经过词法、语法剖析和注解处理两个进程,得到的笼统语法树能够表明一个结构正确的源程序,可是无法确保语义是否符合逻辑。

因而,需求对笼统语法树进行语义剖析,分为两个进程:标示查看、数据及操控流剖析。

剖析与字节码生成的 comile2() 办法的源码,如下图所示:

Android进阶——Javac编译解析

  • 标示查看

标示查看主要是查看变量运用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等等。

例如,下面这段代码:

// 查看变量与赋值之间的数据类型是否匹配(经过)int a = 1;boolean b = true;char c = 2;
    // 查看变量与赋值之间的数据类型是否匹配(不经过)int d = a + b;char e = a + c;
    // 查看变量运用前是否已被声明(不经过)int f = a + g;

除此之外,标示查看还会进行一个称为常量折叠(Constant Folding)的代码优化。

例如,下面这段代码:

// 优化前int a = 1 + 2;// 优化后int a = 3;

标示查看的进口是 attribute() 办法,具体操作由如下两个类完结。

◉ com.sun.tools.javac.comp.Attr ◉ com.sun.tools.javac.comp.Check

  • 数据及操控流剖析

数据及操控流剖析是对程序上下文逻辑更进一步的验证,查看出局部变量运用前是否现已赋值、办法是否有返回值、一切受查反常是否被正确处理、final 变量只能赋一次值等等。

例如,下面这段代码:

// 办法一:public void foo() {    final int a = 0;    a = 1;}

运用 javac 编译会报错,如下图所示:

Android进阶——Javac编译解析

这儿拓宽一下,final 关键字修饰的局部变量只能在编译期进行查看,不能在运行期查看。

由于局部变量表在常量池中没有 CONSTANT_Fieldref_info 的符号引证,并不能存储拜访标志(access_flags),所以在 .class 文件中是不或许知道一个局部变量是否被声明为 final。

数据及操控流剖析的进口是 flow() 办法,具体操作由如下一个类完结。

◉ com.sun.tools.javac.comp.Flow

  • 解语法糖

语法糖(Syntactic Sugar),也称为糖衣语法,指的是在计算机言语中增加某种语法,这种语法对言语的编译成果和功用并没有实践影响,可是却能更便利程序员运用该言语。

语法糖的作用是:削减代码量、增加程序的可读性。

Java 中最常见的语法糖:泛型、边长参数、主动装箱拆箱……

解语法糖的进程便是将语法糖复原回原始的根底语法结构。

解语法糖的进口是 desugar() 办法,具体操作由如下一个类完结。

◉ com.sun.tools.javac.comp.Lower

  • 字节码生成

字节码生成阶段不仅把前面各个进程生成的语法树和符号表转化成字节码指令写到磁盘中,生成 .class文件,还进行少数的代码增加和转化作业。

例如,实例结构器 () 和类结构器 () 便是再字节码生成阶段增加到语法树之中。

除此之外,还有一些代码替换作业用于优化程序某些逻辑的完结方法,也是在字节码生成阶段进行。例如:把字符串的加操作替换成 StringBuffer 或 StringBuilder 的 append()。

字节码生成的进口是 generate() 办法,具体操作由如下两个类完结。

◉ com.sun.tools.javac.jvm.Gen (对语法树进行调整,进行少数的代码增加和转化作业) ◉ com.sun.tools.javac.jvm.ClassWriter (将语法树、符号表转化成字节码,写入到磁盘)

Android进阶——Javac编译解析

以上是Javac编译进程以及简单的解析;Android开发中许多这个的技术知识需求我们一个个去掌握,而网上的资料比较杂乱;找起来比较繁琐,这儿推荐这个文档前往传送直达↓↓↓ :www.6hu.cc/go//?target=htt…里边有30多个技术模块能够参阅学习。

编译HelloWorld

新建一个

HelloJavac.java

Android进阶——Javac编译解析

能够看到HelloJavac目前是还没有编译的。

装备参数:

Android进阶——Javac编译解析

这儿我装备的参数是:-d ModuleFileDirModuleFileDir\target\classes ModuleFileDirModuleFileDir\src\main\java\com\hello\HelloJavac.java 读者依据自己的途径自行更改,宏是从图红框里选的。

编译成果

Android进阶——Javac编译解析