早期(编译期)优化
overview:
- 早期编译期指的是
将*.java文件转变为*.class文件的过程
,如javac编译器。 - 该期间并非优化程序的运行效率。javac做了许多针对java语言编码过程的优化措施改善程序员的编码风格和提高编码效率。
编译过程:
public static void main(String[] args) throws Exception {
com.sun.tools.javac.Main.main(new String[]{"Test.java"});
}
com.sun.tools.javac.main.JavaCompiler
this.delegateCompiler = this.processAnnotations(this.enterTrees(this.stopIfError(CompileState.PARSE, this.parseFiles(var1))), var2);
- 解析:
- 词法分析:将源代码中的字符流转变为token集合
- 语法分析:根据标token列构造抽象语法树–用来描述程序代结构的过程 - 填充符号表:符号表是由一组符号地址和符号信息构成的表格。
- 注解处理器: 插入式注解可以被看作编译器的插件,这些插件可以读取,修改,添加ast中的任意元素,使得我们可以干预编译器的行为。
- 语义分析:主要任务是对语法树结构上正确的源程序进行上下文有关性质的审查。
- 分为标注检查和数据及控制流分析,进一步验证程序上下文逻辑
- 解语法糖 - 字节码生成
- 将前边步骤所生成的信息转化为字节码写到磁盘中,还进行了少量的代码添加和转换工作
- 泛型会在方法的Code属性中的字节码进行擦除,实际上元数据还是保留了泛型信息。 - 编译器并非一个个地编译java文件,而是将所有编译单元的语法树顶级节点输入到待处理列表后再进行编译,因此各个文件之间能够互相提供符号信息。
晚期运行期优化
Overview
- 将字节码编译本地代码,优化就是把那些频繁执行的代码编译为本地代码以提高运行效率
- 解释器和编译器经常配合工作。hotspot虚拟机中,默认采用解释器和其中一个编译器直接配合的方式工作,程序使用哪个编译器,取决于虚拟机运行的模式。
- 使用混合模式从程序启动响应速度和运行效之间取得平衡
- 热点探测:寻找被多次执行的方法或循环体。HotSpot使用方法调用计数器和回边计数器,
以方法为单位进行编译
。 - Client Compiler是一个简单快速的三段式编译器,主要关注点在局部的优化,而放弃了许多耗时较长的全局优化手段。
- Server Compiler是专门面向服务端的典型应用并为服务端的性能配置特别调整过的编译器,也是一个充分优化过的高级编译器
- 虚拟机在代码编译器海未完成之前,都仍然按照解释方式继续执行。
- 这些代码优化都是建立在代码的某种中间表示或者机器码之上,不是建立在java源代码上。
优化措施:
- 语言无关的:公共子表达式消除。就是前边的表达式E已经计算过了,后边再出现同样的表达式就不需要重复计算,直接使用前边的计算结果就行。
- 语言相关:数组边界检查消除。尽量通过编译期的分析来确定数组边界是否会越界。
- 方法内联:就是把方法调用序列整合到一个方法中。来消除方法调用的成本和为其他优化手段建立良好的基础。
但是由于多态性和虚方法的存在,内联并不容易,因此引入“类型继承关系分析”的技术来处理虚方法的内联(就是先选个方法版本内联,不行了再退回去重搞)。 - 逃逸分析:并不是直接优化代码的手段,而是为其他优化手段提供依据的分析技术。
对象逃逸是指:该对象可能被外部方法或者其他线程引用。如果确定该对象不会逃逸(如方法中的局部变量),那么就可以针对它做优化。
- java语言的性能上的劣势,如动态扩展,动态安全,垃圾回收等措施虽然拖慢了运行速度。但是却换取了开发效率。
- 由于c++编译器的所有优化都在编译期间完成,所以就无法进行以运行期性能监控为基础的优化措施,但java可以。
参考