java语言的“编译期”是一段不确定的过程。由于它可能指的是前端编译器把java文件转变成class字节码文件的过程,也可能指的是虚拟机后端执行期间编译器(JIT)把字节码转变成机器码的过程。
以下讨论的编译期优化指的是javac编译器将java文件转化为字节码的过程,而执行期间优化指的是JIT编译器所做的优化。
编译期优化
虚拟机设计团队把对性能的优化集中到了后端的即时编译器(JIT)中,这样能够让那些不是由javac编译器产生的class文件也相同能享受到编译器优化所带来的优点。
可是javac做了很多针对编码过程的优化措施来改善程序猿的编码风格和提高编码效率。很多新生的java语法特性,都是靠编译器的“语法糖”来实现,而不是依赖虚拟机的底层改进来支持。
所以说,java中即时编译器在执行期间的优化过程对于程序的执行来说更重要,而前端编译器在编译期的优化过程对于程序编码来说关系更加密切。
javac编译器的编译过程大致可分为三个步骤:
1.解析与填充符号表过程;
2.插入式注解处理器的注解处理过程;
3.语义分析与字节码生成过程。
以下分别来介绍。
解析与填充符号表;
解析步骤包括了词法分析和语法分析两个过程,首先词法分析是将源码的字符流转变成为标记集合(token)。然后语法分析是根据token序列来构造抽象语法树(一种用来描写叙述程序代码语法结构的树状表示方式)。完毕词法分析和语法分析之后,下一步是填充符号表,符号表是由一组符号地址和符号信息构成的表格,符号表中所登记的信息在编译的不同阶段都要用到(比方语义分析中符号表所登记的内容将用于语义检查和产生中间代码,目标代码生成阶段当对符号名进行地址分配时,符号表是地址分配的根据)。
插入式注解处理器的注解处理过程:
插入式注解处理器能够看做是一组编译器的插件。在这些插件里面。能够读取、改动、加入抽象语法树中的随意元素。假设这些插件在处理注解期间对语法树进行了改动,那么编译器将
回到解析及填充符号表的过程又一次处理,直到全部的插入式注解处理器都没有再对语法树进行改动为止。
语义分析与字节码生成过程:
语法分析之后,编译器获得了程序代码的抽象语法树表示,语法树可以表示结构正确的源程序的抽象,可是无法保证源程序是否符合逻辑,而语义分析主要是对结构上正确的源程序进行上下文有关性质的检查。
1.标注检查
标注检查步骤检查的内容包含诸如变量使用前是否已被声明、变量与赋值之间的数据类型是否可以匹配。等等。另一个重要的动作称为常量折叠也在此阶段完毕。
2.数据及控制流分析
数据及控制流分析是对程序上下文逻辑更进一步的验证,它能够检查出诸如程序局部变量在使用前是否有赋值、方法的每条路径是否有返回值、是否全部的受查异常都被正确处理了等问题。
3.解语法糖
语法糖是指在计算机语言中加入某种语法,这样的语法对语言的功能并没有影响,可是更方便程序猿使用。
java中的泛型。变长參数,自己主动拆箱与装箱,条件编译等就属于语法糖。它们在编译阶段就被还原成简单的语法结构(比方List<String>和List<Integer>在执行期间事实上是同一个类)。
4.字节码生成
此过程是javac编译过程的最后一个阶段,字节码生成阶段将之前各个步骤所生成的信息转化成字节码写到磁盘中,另外还进行少量的代码加入和转换工作。
执行期优化
在部分商用虚拟机中。java程序最初是通过解释器进行解释执行的,当虚拟机发现某个方法或代码块执行特别频繁,就会把这些代码认定为“热点代码”,为了提高热点代码的执行效率,在执行时,虚拟机就会把这些代码编译成与本地平台
相关的机器码,并进行各种层次的优化,完毕这个任务的编译器称为即使编译器或JIT编译器。
即时编译器并非虚拟机必须的部分,可是即时编译器编译性能的好坏、代码优化程度的高低确是衡量一款商用虚拟机优秀与否的最关键的指标之中的一个。
众多主流的虚拟机都同一时候包括解释器和JIT编译器,解释器与JIT编译器各有优势:当程序须要迅速启动和运行时,解释器能够首先发挥作用,省去编译的时间,马上运行。当程序运行后,随着事件的推移。JIT编译器逐渐发挥作用。把越来越多的代码编译成本地代码之后。能够获取更高的运行效率。
会被即时编译器编译的热点代码有两类:
1.被多次调用的方法体。
2.被多次调用的循环体。
即时编译器会以整个方法作为编译对象。将其编译成机器码。
这样的编译方式由于编译发生在方法运行过程之中,因此被称作栈上替换(OSR)。
推断一段代码是否是热点代码的方式(热点探測)有两种:
1.基于採样的热点探測:
此方法会周期性检查各个线程的栈顶。假设发现某个或某些方法常常出如今栈顶。那么这种方法就是热点方法。此方法的缺点是非常难精确地确认一个方法的热度。easy受到诸如线程堵塞等因素影响。
2.基于计数器的热点探測:
此方法会为每一个方法甚至是代码块建立计数器。统计方法的运行次数,假设运行次数超过一个阀值就觉得它是热点方法。
注:默认设置下,运行引擎并不会同步等待编译请求完毕。而是继续进入解释器依照解释方式运行字节码,直到提交的请求被编译器编译完毕。当编译工作完毕之后,这种方法的调用入口地址就会被系统自己主动改写成新的地址,下一次调用该方法时就会使用已编译的版本号。也就是说,在编译器还未完毕之前,运行引擎仍依照解释方式继续运行。而编译动作则在后台的编译线程中进行。
优化技术:
一般来说即时编译器所产生的本地代码会比javac产生的字节码更优秀。即时编译器採用了一系列的技术来优化代码。比方公共子表达式消除,数组范围内检查消除。方法内联,逃逸分析等。