JVM-程序编译与代码早期(编译期)优化
早期(编译期)优化
一、Javac编译器
1.Javac的源代码与调试
Javac的源代码放在JDK_SRC_HOME/langtools/src/shares/classes/com/sun/tools/javac中,除了JDK自身的API之外,就只引用了JDK_SRC_HOME/langtools/src/shares/classes/com/sun/*里面的代码,调试环境建立起来简单方便,因为基本上不需要处理依赖关系。
编译过程大致可以分成3个过程:
(1)解析与填充符号表过程
(2)插入式注释处理器的注解过程处理
(3)分析与字节码生成过程
Javac编译动作的入口是com.sun.tools.javac.main,JavaCompikler类,上述3个过程的代码逻辑集中在这个类的compiler()和compiler2()方法中。
2.解析与填充符号表
解析步骤包括词法分析和语法分析两个过程
(1)词法、语法分析
词法分析是将源代码的字节流变成标记(Token)集合,单个字符是程序编码过程的最小元素,而标记则是编译过程的最小元素。
在Javac的源代码中,词法分析过程由com.sun.tools.javac.parser.Scanner类来实现。
词法分析是根据Token序列构造抽象语法树的过程,抽象语法是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构。
语法分析过程由com.sun.tools.javac.parser.Parse类来实现,这个阶段产生出抽象语法树有com.sun.tools.javac.tree.JTree类表示,经过这个步骤之后,编译器就基本不会再对源代码文件进行操作了,后续的操作都建立在抽象语法树上。
(2)填充符号表
符号表(Symbol Table)是由一组符号地址和符号信息构成的表格。
在语法分析中,符号表所登记的内容将用于语法分析检查和产生中间代码。
在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据。
在Javac源代码中,填充符号表的过程由com.sun.tools.javac.compiler.Enter类来实现,此过程的出口是一个待处理列表(ToDoList),包含;了每一个编译单元的抽象语法树的顶级节点以及package-info-java的顶级节点。
3.注解处理器
在Javac源码中,插入式注解处理器的初始化过程是在initProcessAnnotations()方法中完成的,而它的执行过程则是在ProcessAnnotations()方法中完成的。这个方法判断是否有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.Processing.JavacProvcessingEnviroment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理。
4.语义分析与字节码生成
(1)标注检查:内容包括诸如变量使用前后是否已被声明,变量与赋值之间的数据类型是否能够匹配等。在标注检查步骤中,还有一个重要的动作,称为常量折叠。
标注检查步骤在javac源代码中实现类是com.sun.tools.javacComp.Attr类和com.sun.tools.javac.comp.Check类。
(2)数据及控制分析
对程序上下文逻辑更进一步的验证,它可以检查出诸如程序局部变量在使用前后是否有赋值,方法的每一条路径是否都有返回值,是否所有的受检查异常都被正确出来了等问题。
在Javac的源代码中,数据及控制流分析的入口是flow()方法,具体操作是由com.sun.tools.javac.comp.Flow类来完成的。
(3)解语法糖
语法糖(Syntatic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便使用。
在Javac的源代码中,解语法糖的过程由的desugar()方法触发,在com.dun.tools.javac.comp.TransTypes类和com.sun.tools.javac.comp.Lower类中完成。
(4)字节码生成
字节码生成是Javac编译过程的最后一个阶段,在Javac源代码里面有com.sun.tolls.javac.jvm.Gen类来完成。
完成了语法树的遍历和调整之后,就会把填充了所有需要信息的符号表交给com.sun.tolls.javac.jvm.ClassWrite类,由这个类的WiteClass()方法输出字节码,生成最终的class文件。到此为止整个编译过程就结束了。
二、Java语法糖
1.泛型与类型擦除
C#里面泛型无论在程序源码中,编译后的IL中,货值运行期的CLR中,都是切实存在的,List<int>与List<string>就两个不同稍微类型,它们在系统运行期生成,有自己的虚方法表和,类型数据,这种实现机制称为类型膨胀,基于这种方法实现的泛型称为真是泛型。
Java语言中的泛型不一样,它只在程序源代码中存在,在编译后的字节码文件中,就已经替换为原来的原生类型了,并且在相应的地方插入了强制转型代码,因此,对于运行期的Java语言来说,ArrayList<String>与ArrayList<int>就是同一个类,所以泛型技术实际上是Java语言的一颗语法糖,java语言中的泛型实现方法称为类型擦除,基于这种方法的泛型称为伪泛型。
虚拟机规范中引入了诸如Signature,LocalVariableType等新的属性用于解决伴随泛型而来的参数类型识别问题。
2.自动装箱、拆箱与遍历循环
自动装箱、拆箱在编译之后转化成了对应的包装盒还原方法,而遍历循环则把代码还原成迭代器的实现。
包装类的“==”运算在不遇到算术符运算的情况下不会自动拆箱,以及它们equals()方法不处理数据转型的关系。
3.条件编译
Java语言可以进行条件编译,方法就是使用条件为常量的if语句。
Java语言中条件编译的实现是,Java语言的一颗语法糖,根据布尔常量的真假,编译器将会把分支中不成立的代码清除掉。这一项工作将在编译器解除语法糖阶段(com.sun.tools.javac.comp.Lower类中)实现。