天天看点

DepthJVM-编译期优化

1.Java编译器编译过程 1.1 解析与填充符号表 词法、语法分析: 填充符号表: 1.2 插入式注解处理器的注解处理过程 用于处理注解,类似于一组编译器插件,可以读取、修改、添加语法树中的任意元素,如果对语法树进行了修改编译器将回到解析及填充符号表过程重新处理,直至没有修改位置,每一个循环称为一个Round(回环) 插入式注解处理器初始化过程在initProcessAnnotations(),执行过程在processAnnotations(),这个方法判断是否还有新的注解处理器需要执行,如果有通过com.sun.tools.javac.processing.JavacProcessingEnvironment.doProcessing()方法生成一个新的JavaCompiler对象对编译器后续步骤进行处理 1.3 语义分析与字节码生成 语法分析后编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的,而语义分析的主要任务就是对源程序进行上下文有关性质的审查,如类型审查。(bs:语法分析保证源程序结构正确,语义分析保证源程序逻辑正确) 语义分析过程:标注检查(attribute)、数据及控制流分析(flow) 标注检查:包括诸如变量使用前是否声明、变量与赋值之间类型是否匹配等。标注检查中有一个动作成为常量折叠,如int a=1+2会被折叠为int i=3显示到语法树。实现类com.sun.tools.javac.comp.Attr和Check 数据及控制流分析:对程序上下文逻辑做进一步验证,检查诸如程序局部变量使用前是否赋值、方法的每条路径是否由返回值、是否所有受检异常都被正确处理等。编译时该动作与类加载时目的一致,但校验范围有所不同,如final局部变量。局部变量在常量池没有CONSTATNT_Field_info符号引用,Class文件不知道局部变量是否final,因此final局部变量对运行期没有影响,变量的不变性仅仅由编译器在编译期间保障。入口是flow()方法,实现类com.sun.tools.javac.comp.Flow 解语法糖:虚拟机不支持语法糖,编译阶段还原回简单的基础语法接口,称为解语法糖。入口是desugar(),实现类com.sun.tools.javac.comp.TransTypes和Lower 字节码生成:实例构造器<init>和类构造器<cinit>加入语法树,这两个构造器的产生过程其实是一个代码收敛的过程层,编译器会把语句块、变量初始化、调用父类的实例构造器等方法收敛到<init>和<cinit>方法中,并且保证一定是按执行父类的实例构造器,然后变量初始化,最后语句块的执行顺序,实现方法com.sun.tools.javac.jvm.Gen.normalizeDefs() 完成语法树遍历和调整之后,把填充了所有所需信息的符号表交给com.sun.tools.javac.jvm.ClassWriter.writeClass(),输出字节码,生成最终的Class文件,编译过程结束 2.Java语法糖 2.1 泛型与类型擦除 本质是参数化类型的应用,也就是所操作的数据类型被指定为一个参数,可以用在类、接口、方法的创建中,称为泛型类、泛型接口、泛型方法。泛型思想是用于提升语义准确性 真实泛型:泛型在源码、编译后代码、运行时代码中都切实存在,如C# 伪泛型:泛型只在源码中存在,编译后的字节码中已经替换为原生类型,如Java。Java中泛型实现方法称为类型擦除 实例:void say(List<String> list)与void say(List<Integer> list)不能共存于Class文件,编译不通过;int say(List<String> list)与String say(List<Integer> list)可以编译执行,因为两方法返回值不一样,所以方法描述符不一样,因此能够共存于Class文件(方法重载要求方法具备不同的特征签名,返回值不包含在特征签名中,因此不参与重载) Java泛型引入后,引入了Signature、LocalVariableTypeTable等新属性用于解决伴随泛型而来的参数类型的识别问题,Signature作用是存储一个方法在字节码层面的特征签名,保存的参数类型并不是原生类型,而是包括了参数化类型的信息。从Signature属性可知擦除法只是对方法的Code属性中的字节码进行擦除,实际上元数据中依然保留了泛型信息,因此可以通过反射取得参数化类型数据 2.2 自动装箱、拆箱与遍历循环foreach Integer itg=1编译后为Integer itg=Integer.valueOf(1)(自动装箱),int it=itg编译后为int it=itg.intValue()(自动拆箱) foreach编译后还原为迭代器Iterator 注:装箱类型equals只于自身类型比较,其他类型都返回false 2.3 条件编译 使用条件为常量的if语句(如if(true){}),编译器会根据布尔常量值将不成立(不可达)的分支消除,只能写在方法体内部,实现语句基本块级别的条件编译

继续阅读