编译期与编译器
Java语言的编译期可分为:
- 前端编译器把.java文件转变为.class文件的过程(Javac\ECJ)
- 虚拟机的后端运行时编译器(JIT编译器,Hotspot的C1\C2编译器)把字节码转化为机器码的过程
- 静态提前编译器(AOT编译器)直接把*.java文件编译为本地机器代码的过程
Javac这类编译器对代码的运行效率几乎没有任何优化措施,但做了许多针对Java语言编码过程的优化措施来完善程序员的编码风格和提高编码效率。 虚拟机对性能的优化集中在后端的即时编译器中。
前端编译器编译过程
- 解析与填充符号表过程
- 词法分析、语法分析:将源代码的字符流转变为标记(Token)集合,根据Token序列构造抽象语法树的过程。
- 填出符号表:由一组符号地址和符号信息构成的表格
- 插入式注解处理器的注解处理过程
- 分析与字节码生成过程:对结构上正确的源程序进行上下文有关性质的审查
- 标注检查:检查的内容包括诸如变量使用前是否已经声明、变量与赋值之间的数据类型是否能够匹配等。
- 常量折叠:
- 数据及控制流分析:对程序上下文逻辑更近一步的验证。
- 解析语法糖:虚拟机在编译阶段将语法糖(泛型、变长参数、自动装箱\拆箱)还原回简单的基础语法结构,这个过程称为解语法糖。
- 字节码生成
- 标注检查:检查的内容包括诸如变量使用前是否已经声明、变量与赋值之间的数据类型是否能够匹配等。
在 JDK 1.5,Java语言提供了对注解的支持,注解是在运行期间发挥作用的。 在 JDK 1.6,实现了JSR-269规范,提供了一组插入式注解处理器的标准API在编译期间对注解进行处理,可视为编译器的插件,如Lombok。在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理。
局部变量与字段(实例变量、类变量)是有区别的,它在常量池中没有CONSTANT_FIELD ref_info的符号引用,自然就没有访问标志的信息。因此将局部变量声明为final,对运行期没有影响,变量的不变形仅仅由编译器在编译期保障。
Java语法糖
-
泛型是JDK 1.5 的新增特性。其本质是参数化类型的应用,也就是说所操作的数据类型被指定为一个参数。在Java中,泛型只在程序源码中存在。在编译后的字节码文件中,就已经替换为原来的原生类型了,并且在相应的地方插入了强制转型代码。
- 自动装箱、拆箱与遍历循环(foreach循环)
- 循环遍历(Foreach)把代码还原成迭代器的实现,所有被循环遍历的类实现Iterable接口。
- 条件编译: 根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉
后期编译器
在 Hotspot中,Java程序最初是通过解释器进行解释执行,当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。 为了提高热点代码的执行效率,在运行时,将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器 Hotspot中内置了两个即时编译器:
- Client Compiler(C1)
- Server Compiler(C2)