JVM学习--(六)前端编译与优化

JVM学习–(六)前端编译与优化

一般说的“编译期”是比较宽泛的概念,主要可以指三个方面:
1️⃣前端编译器。指的是吧.java文件转化成 .class文件的过程。
2️⃣即时编译器。指的是运行期把字节码转成机器码的过程。
3️⃣提前编译器。直接把程序编译成与目标机器指令集相关的二进制代码。
这里主要指的是前端编译器,由于在这里对于效率的优化措施基本上是没有的,优化主要针对的是程序员的编码效率和语言使用者的幸福感。

一.javac编译器

javac编译器除了使用了JDK自身的标准类库以外,就只引用了src/share/classes/com/sun/*的代码,从javac代码的总体结构来看,编译过程大致可以分成1个准备过程和3个处理过程,分别是:
1️⃣准备过程:初始化插入式注解处理器。
2️⃣解析和填充符号表的过程:
1.词法、语法分析,将字符流转变成标记集合,构造出抽象语法树。2.填充符号表,产生符号地址和符号信号。
3️⃣插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段。只有这里可以diy。
4️⃣分析与字节码生成过程,包括:
1.标注检查。2.数据流及控制流分析。3.解语法糖。4.字节码生成。
*注:若第3步中有产生新的符号,需要从第2步重新开始。
整个编译过程可以总结为:
准备过程:初始化插入式注解处理器—过程1.1:词法分析,语法分析—过程1.2:输入到符号表—过程2:执行注解处理—过程3.1:标注—过程3.2:数据流分析—过程3.3:解语法糖—过程3.4:生成字节码

1.解析和填充符号表

①词法、语法分析
词法分析是指将源代码的字符流转变成标记集合的过程。字符是程序编写时的最小元素,而标记才是编译时的最小元素。
语法分析是根据标记序列构造抽象语法树的过程。
抽象语法树是一种用来描述程序代码语法结构的树形表示方式,每一个节点都代表着程序代码中的一个语法结构。
②填充符号表
符号表是一组符号地址和符号信息构成的数据结构。该过程的产出物是一个待处理列表。

2.注解处理器

在jdk6之后设计了一组被称为“插入式注解处理器”的标准API,可以提前至编译器对代码中特定注解进行处理。可以看做是一组编译器的插件,当这些插件工作时,允许读取、修改、添加抽象语法树的任意元素。

3.语义分析与字节码生成

语义检查:对抽象语法树上正确的源程序进行上下文相关性质的检查,例如:类型检查、控制流检查、数据流检查。
①标注检查
内容包括了变量使用前是否已经被声明、变量和赋值之间的数据类型是否匹配,这其中还包括一个小优化,被称为常量折叠。
②数据及控制流分析
这是对程序上下文逻辑更进一步的验证。检查例如程序局部变量在使用前是否赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理等,基本的功能可以看做是跟类加载时的数据和控制流分析类似。
比如用final修饰的局部变量,这跟普通的局部变量在class文件中的表达都一样,对于运行期没有任何影响,因此此类错误需要在数据及控制流分析中指出。
③解语法糖
④字节码生成
不仅仅是把前面各个步骤生成的信息(语法数,符号表)转化为字节码指令写到磁盘中,编译器还进行了少量的代码添加和转换工作。
例如实力构造器()方法以及类构造器方法的生成。

二.语法糖

语法糖不能提供实质性的功能改进,但是它们能提高效率,或提高语法的严谨性,或者能减少代码出错的机会。

1.泛型

泛型的本质是参数化类型或者参数化多态。
①java与C#的泛型
java的泛型被称为“类型擦拭式泛型”,C#选择的是“具现化式泛型”。C#里面的泛型无论是在程序源码里面、编译后的中间语言表示或者是运行期的CLR里面都是切实存在的。java语言中的泛型只是在源码中存在的,在字节码中所有的泛型会被替换成原来的裸类型。
两者对比:
1️⃣C#的泛型无论是使用效果还是运行效率上都是全面领先于java的泛型(主要是因为java的泛型会导致无数结构包装类的装拆箱)。
2️⃣java泛型的唯一优势在于泛型的实现只需要在javac编译器上做出改进即可。其中java的历史因素(需要完全向后兼容)导致了java的泛型只可以采用这样的方式。
3️⃣java泛型使得运行期无法得到泛型类型信息,会让一些代码变得十分冗长。
②java代码面向对象优雅型的丧失
当泛型遇到重载时,如果两者的参数都是泛型而且返回值相同时,这段代码将不会被编译执行,因为泛型类型在编译之后都被擦拭,导致了两个方法一模一样而无法重载。
但如果两者的返回值类型不同却可以重载,这是因为加入了不同的返回值使得可以共存于一个class文件中,通过Signature来储存一个方法在字节码层面的特征签名,这其中包括了返回值。只有jdk6中才可以编译。
③自动拆装箱与遍历循环
需要注意的是包装类的“==”运算如果不遇到算术运算的情况下不会拆装箱,以及equals不会处理数据类型转换。
*integer中的比较尽量用equals,因为=只能比较[-128,127]的数据。
④条件编译
条件不成立的代码会在编译期间被去掉,在字节码中是不存在这段代码的。

你可能感兴趣的:(JVM)