早期(编译期)优化——Javac编译器


“编译期”是一个不确定的过程:

  • 前端编译器:把.java转换为.class的过程。如Sun的javac、Eclispe JDT的ECJ
  • 后端运行期间编译器:将.class转换为机器码的过程。Hotspot VM的C1、C2编译器。
  • 静态提前编译器:直接把.java转换为本地机器码的过程。CNU Compiler for the Java(GCJ)、Excelsior JET。

一、Javac的源码与调试

Javac的源码存放在

JDK_SRC_HOME/langtools/src/share/classes/com/sun/tools/j>avac中。还有javac不像hotspot那样是用C++实现的,javac是纯>java代码实现的。

编译过程:

java代码
解析与填充符号表
注解处理
分析与字节码生成
机器码

JDK7中Javac编译过程的主体代码(编译动作的入口是som.sun.tools.javac…main.JavaCompiler):
compile

try {
            //准备过程:初始化插入式注解处理器
            initProcessAnnotations(processors);

            // These method calls must be chained to avoid memory leaks
            delegateCompiler =
                processAnnotations( //2:执行注解过程
                    enterTrees(stopIfError(CompileState.PARSE,//过程1.2:输入到符号表
                            parseFiles(sourceFileObjects))),//过程1.1:词法分析、语法分析
                    classnames);

            delegateCompiler.compile2();//过程3:分析及字节码生成
            delegateCompiler.close();
            elapsed_msec = delegateCompiler.elapsed_msec;
        }

compile2():

case BY_TODO:
                while (!todo.isEmpty())
                    generate(desugar(flow(attribute(todo.remove()))));//过程3.1-attribute:标注;过程3.2-flow:数据流分析;过程3.3-desugar:解语法糖;过程3.4-generate:生成字节码
                break;

二、解析与填充符号表

2.1 词法分析、语法分析

词法、语法分析对应parseFiles()
词法分析:

词法分析就是将源码转为(Token)标记,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键词、变量名、字面量、运算符都可以成为标记。

语法分析:

就是根据Token构造抽象语法树的过程,语法树的每一个节点都代表着程序中的一个语法结构,例如包、类型、修饰符、运算符、接口、返回值甚至代码注释。

2.2 填充符号表

填充符号表对应enterTrees()
符号表是由一组符号地址符号信息构成的表格。符号表在编译的不同阶段都要用到。


三、注解处理器

注解与代码一样都是在运行期间发挥作用的。插入式注解处理器可以在编译期间对注解进行处理。其工作原理如下:

如果在注解处理期间对语法书进行了修改,那么编译器将进行一次循环Round,回到解析及填充符号表阶段。直到注解处理期间不在修改语法树。

插入式注解处理器的初始化是在==initProcessAnnotations()中完成的,而注解处理过程是在processAnnotations()==方法中完成的。


四、语义分析与字节码生成

语法树表示一个结构正确的程序的抽象,但无法保证源程序是符合逻辑的。所以就有了语义分析。语义分析包括标注检查数据及控制流分析

例子:

int a=1;
boolean b=false;
char c=2;

int d=a+c; //1
int d=b+c;//2
char d=a+c;//3

从语法、词法分析中,这是没毛病的,但是不和逻辑啊!!!1,2,3中只有1在语义上没毛病。

4.1 标注检查

对应的是attribute()
标注检查:

标注检查的内容有检查变量在使用前是否已被声明、变量与赋值类型是否匹配等。

标注检查中,还有一个重要的动作——常量折叠。例子:

int a=1+2;

这个在词法解析、语法解析之后的语法树中,'1’、‘+’、‘2’是存在的。然而在标注检查时,经过常量折叠后,会被折叠为字面量‘3’。

4.2 数据及控制流分析

对应的是flow()
数据及控制流解析:

这是对程序上下文逻辑更近一步的验证。它可以检查出程序局部变量在使用前是否被赋值、方法的每条路径是否有返回值、是否所有的可检查异常都被正确处理等问题。

前端编译器和后端运行期间编译器在数据及控制流分析的目的上是一致的,但校验范围有所区别。有一些校验项只有在编译期或运行期才能进行。例子:

//带有final
public void foo(final int arg){
	final int var=0;
}

//没有final修饰
public void foo(int arg){
	int var=0;
}

这两段代码编译出来的class文件是没有区别的。局部变量声明为final,对运行是没有影响的,变量的不变性由编译器在编译期保障

4.3 解语法糖

对应的是desugar()
语法糖只是让程序员更好的编写代码。Java中最常见的语法糖就是泛型、变长参数、自动装箱/拆箱。虚拟机运行时并不支持这些语法。所以在编译阶段还原会简单的基础语法结构这一过程就是解语法糖。

4.4 字节码生成

对应generate()
字节码生成:

除了将前面各个步奏生成的信息(语法树、符号表)转换为字节码写到磁盘中,编译器还进行了少量的代码添加(如实例构造器<init>()、类构造器<clinit>)和转化工作(如把字符串的加操作转化为StringBuffer或StringBuilder的append()操作)。

猜你喜欢

转载自blog.csdn.net/wobushixiaobailian/article/details/83904394
今日推荐