深入理解jvm --第十章 早期(编译期)优化

深入理解jvm --第十章 早期(编译期)优化

 

 

概述

深入理解jvm --第十章 早期(编译期)优化

Java语言的编译期是一段不确定的操作过程

 

备注:

(1)它可能是指一个前端编译器把*.java文件转变成*.class文件的过程

(2)也可能是指虚拟机的后端编译器(JIT编译器)把字节码转变成机器码的过程

(3)还可能是指使用静态的提前编译器(AOT编译器)直接把*.java文件编译成本地代码的过程

 

3类编译过程中有代表性的编译器

深入理解jvm --第十章 早期(编译期)优化

前端编译器:Sun的javac,Eclipse JDT中的增量式编译器(ECJ)

 

备注:

(1)本章中仅讨论第一类编译过程

(2)javac这类编译器对代码的运行效率几乎没有影响,但是javac做了许多针对java语言编码过程的优化

(3)对性能的优化集中在后端的即时编译器中

 

JIT编译器:HotSpot VM的C1,C2编译器

AOT编译器:GNU Compiler for the Java(GCJ),Excelsior JET

javac编译器

深入理解jvm --第十章 早期(编译期)优化

 

注释:

javac是由Java语言编写的程序

javac的源码与调试

深入理解jvm --第十章 早期(编译期)优化

javac编译过程

深入理解jvm --第十章 早期(编译期)优化

 

备注:

(1)javac编译动作的入口是com.sun.tools.javac.main.JavaComplier类

(2)三个过程的代码逻辑集中在入口类的complile()和compile2()方法中

解析与填充符号表

深入理解jvm --第十章 早期(编译期)优化

词法,语法分析

 

备注:

--词法分析

(1)词法分析将源代码的字符流转变为标记(Token)集合

(2)单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素

(3)关键字,变量名,字面量,运算符都可以成为标记

(4)在javac源码中,词法分析过程是由com.sun.tools.javac.parser.Scanner类来实现的

--语法分析

(1)语法分析是根据Token序列构造抽象语法树的过程

(2)抽象语法树是用来描述程序代码语法结构的树,每一个节点都代表着程序代码中的一个语法结构(例如包,类型,修饰符,运算符,接口,返回值甚至代码注释等)

(3)在javac的源码中,语法分析过程由com.sun.tools.javac.parser.Parser类实现的

(4)这个阶段产出的抽象语法树由com.sun.tools.javac.tree.JCTree类表示

(5)之后编译器基本不会再对源码文件进行操作了,后续的操作都建立再抽象语法树之上

 

填充符号表

 

备注:

(1)通过enterTrees()方法完成

(2)符号表是由一组符号地址和符号信息构成的表格

(3)在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码

(4)在目标代码生成阶段,当对符号名进行分配时,符号表是地址分配的依据

(5)符号表填充过程是由com.sun.tools.javac.comp.Enter类实现的

(6)此过程的出口是一个待处理列表(To Do List),包含了每一个编译单元的抽象语法树的顶级节点以及package-info.java(如果存在的话)的顶级节点

注解处理器

 

备注:

(1)在JDK1.5之后,java语言提供了对注解的支持,这些注解与普通Java代码一样,是在运行期间发挥作用的

(2)在JDK1.6中提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,可以读取,修改,添加抽象语法树中的任意元素。如果在注解处理期间对语法树做了修改,编译器将回到解析及填充符号表的过程重新处理

(3)插入式注解处理器的初始化过程是在initPorcessAnnotation()方法中完成的,而它的执行过程是在processAnnotations()中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理

语义分析与字节码生成

深入理解jvm --第十章 早期(编译期)优化

 

备注:

语法分析后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的

语义分析

深入理解jvm --第十章 早期(编译期)优化

标注检查

 

备注:

(1)由attribute()完成

(2)检查内容包括变量使用前是否已被声明,变量与赋值之间的数据类型是否匹配等

(3)常量折叠

(4)标注检查步骤在javac中的实现类是com.sun.tools.javac.comp.Attr类和com.sun.tools.javac.comp.Check类

数据及控制流分析

 

备注:

(1)由flow()方法完成

(2)对程序上下文逻辑更进一步的验证,可以检查出程序局部变量在使用前是否赋值,方法的每条路径是否都有返回值,是否所有的受检查异常都被正确处理等

(3)与类加载时期的数据及控制流分析的目的基本上是一致的,但校验范围有所区别(例如final局部变量的不变性只在编译期检查)

 

 

解语法糖

 

备注:

解语法糖的过程由desugar()方法触发,在com.sun.tools.javac.comp.TransTypes类和com.sun.tools.javac.comp.Lower类中完成

字节码生成

 

备注:

(1)是javac编译过程的最后一个阶段

(2)由com.sun.tools.javac.jvm.Gen类完成

(3)除了把前面各个步骤生成的信息(语法树,符号表)转化成字节码写道磁盘中,编译器还进行了少量代码添加和转换工作

(4)实例构造器<init>()方法和类构造器<cinit>()方法在这个阶段添加到语法树上,这个工作由Gen.normalizeDefs()方法实现

(5)其他用于优化程序的实现逻辑的代码替换工作,如把字符串的加操作替换为StringBuffer或StringBuilder的append()操作等

(6)最后把填充了所有所需信息的符号表交给com.sun.tools.javac.jvm.ClassWriter类,由它的writeClass()方法输出字节码生成Class文件

 

Java语法糖的味道

深入理解jvm --第十章 早期(编译期)优化

泛型与类型擦除

 

备注:

(1)java的泛型是伪泛型,在编译后的字节码文件中被替换为原生类型,并在相应地方插入了强制转换代码

(2)引入了Signature,LocalVariableTypeTable等新属性以获取传入的参数化类型,Signature用于存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型不是原生类型,而是包括了参数化类型的信息

(3)擦除所谓的擦除,仅仅是对方法的Code属性中的字节码进行擦除,实际上元数据中还保留了泛型信息,这也是能通过反射取得参数化类型的根本依据

 

自动装箱,拆箱与Foreach循环

 

备注:

(1)自动装箱,拆箱在编译之后被转化成了对应的包装和还原方法(如Integer.valueOf()与Integer.intValue())

(2)Foreach被还原为迭代器循环for循环

(3)可变长参数被还原为数组类型

(4)包装类的"=="运算在不遇到算术运算的情况下不会自动拆箱,它们的equals()方法也不处理数据类型转型的关系

条件编译

 

备注:

使用条件为常量的if语句可以实现条件编译