目录
早期(编译期)优化
优化手段主要是提升程序的编码效率。
1.概述
3类编译过程:
- 前端编译器,如Sun的javac
把*.java转为*.class的过程。
- JIT编译器(即时编译) 如:HotSpot VM 的C1\C2编译器。
字节码转为机器码。
- AOT编译器(静态提前编译)
直接吧*.java转为机器代码的过程。
2.javac编译器
Sun的javac的编译过程分为3个:
- 解释和填充符号表过程
- 插入式注解处理器的注解处理过程
- 分析和字节码生成过程。
2.1 解析和填充符号表
- 2.1.1 词法、语法分析
<>词法分析:字符流转为标记(Token)集合。
实现:com.sun.tools.javac.parser.Scanner
<>语法分析:根据Token构建抽象语法树。
语法分析实现:com.sun.tools.javac.parser.Paraser
----语法树的节点 包括包、类型、修饰符、运算符、接口、返回值、注释等。
- 2.1.2 填充符号表
符号表:符号地址和符号信息构成的表格。在编译的不同阶段都要用到。
<>实现:com.sun.tools.javac.comp.Enter。出口是一个(To Do List)
2.2 注解处理器
注解和一般的java代码一样,在运行期间发挥作用。
2.3 语义分析和字节码生成
语义分析:主要任务是对结构正确的源程序进行上下文有关性质的审查。比如类型审查。
语义分析包括标准检查和数据及控制流分析。
- 2.3.1 标注检查
检查内容包括诸如使用变量之前是否已经被声明、变量与赋值2之间的数据类型是否匹配等。会进行常量折叠。
<>实现 com.sun.tools.javac.comp.Attr 和 com.sun.tools.javac.comp.Check
- 2.3.2 数据及控制流分析
检查诸如局部变量使用前知否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理等。
<>实现 com.sun.tools.javac.comp.Flow
- 2.3.3 解语法糖
对语言的功能没有影响,当时方便程序员的使用。
<>实现 desugar() com.sun.tools.javac.comp.TransTypes 和 com.sun.tools.javac.comp.Lower
- 2.3.4 字节码生成
<>实现 com.sun.tools.javac.jvm.Gen
<>将前面各个步骤生成的信息转华为字节码写入磁盘中,还会进行一些代码转换和添加工作。
代码替换:比如把字符串的加操作替换为 StringBuffer 或者 StringBuilder 的append() 操作。
完成语法树的遍历和调整之后,就把填充了所有信息的符号表经过com.sun.tools.javac.jvm.ClassWriter 的writerClass()方法生成最终的Class文件。
3. 语法糖
3.1 泛型和类型擦除
泛型的本质是参数化类型的应用。
JAVA中泛型的实现方法称为类型擦除。
3.2 自动装箱、自动拆箱和遍历循环
遍历循环把代码还远程迭代器的实现。
包装类的‘==’方法在不遇到算术运算的情况下不会自动拆箱。他们的equals()方法不处理数据类型的关系。
3.3 条件编译
使用条件为常量的if语句。
3.4 其他
内部类、枚举类、断言语句、对枚举和字符串的switch支持、try语句中的定义和关闭资源等也是语法糖。
晚期(运行期)优化
1. 概述
2. HotSpot 虚拟机内的即时编译器
2.1解释器和编译器
解释器节约内存、编译器提升效率。
C1编译器:Client Compiler、C2编译器:Sever Compiler
分层编译(java7的Sever模式虚拟机的默认编译策略):
<1>第0层,解释执行,不开启性能监控,出发第一层
<2>第1层,也叫C1编译。简单可靠的优化,有必要的话开启有监控的逻辑
<3>第2层(或2层以上)也叫C2编译,会用一些好市场的优化,甚至根据性能监控进行一些不可靠的激进优化。
2.2 编译的对象和触发的条件
编译对象:
- 被多次调用的方法
- 被多次调用的循环体(栈上替换\OSR编译)
热点探测方法:
- 基于采样的热点探测
- 基于计数器的热点探测
HotSpot虚拟机的两类计数器:
- 方法调用计数器
记录方法被调用的次数(不是绝对次数,会有半衰期)
- 回边计数器
循环体代码的执行次数(回边次数。绝对次数)。目的是为了出发OSR编译。出发OSR之后会把回边计数器的值调低一点,以便继续在解释器中执行循环。
2.3 编译过程
后台执行编译的过程做了什么?
C1:(简单快速的三段式编译器):主要关注点在于局部性的优化,放弃了耗时长的全局优化手段。
- 字节码构造成一种高级中间代码(HIR),HIR使用静态单分配(SSR)的形式代表代码值。之前会进行基础优化
- 从HIR生成低级中间代码表示(LIR),会进行另一些优化,如:空值检测消除、范围检查消除
- 寄存器分配、窥孔优化、产生机器码
C2:会自行所有经典的优化动作,会是是一些与JAVA语言特性密切相关的优化技术、会进行一些不稳定的激进优化。
3.编译优化技术
方法内敛的重要性由于其他优化措施,主要目的有两个:
- 去除方法调用的成本
- 为其他优化建立良好的基础
3.1 公共子表达式消除
如果一个表达式E以及计算过来,并在先前的计算到现在E中的所有变量都没有发生变化,对于这种公共子表达式(E),没必要花时间进行计算,直接用之前的计算结果代替即可。
3.2数组边界检查消除
如果编译器只要通过数据流分析就可以判定循环变量的曲子范围永远不会越界,在讯哈unzhongjiukeyiba数组的上下界检查消除。
隐式异常处理:
采用try...catch 来捕获异常在进行处理。代价是:异常发生时他哦从用户态转为内核态处理,结束后返回用户态。
3.3 方法内联
为解决虚方法内敛问题,引入类型继承关系分析:(CHA)
- 内联时,如果是非虚方法,直接内联
- 是虚方法,找CHA查询是否有多个目标版本,如果只有一个版本,可以进行内联,该种方法属于激进优化,需要预留‘逃生门’,称为守护内联。采用该优化结果直到新加载的类导致继承关系变化,这解释执行并重新编译。
- 如果CHA查询有多个版本,可用内联缓存完成方法内联。
3.4 逃逸分析
基本行为是分析对象动态作用域。
- 方法逃逸:被外部方法引用
- 线程逃逸:被外部线程访问
如果一个对象不会逃逸到方法或者线程外,可以采取的高效优化方法:
- 栈上分配
对象在栈上分配内存,并随着线程结束自动销毁。不需要垃圾回收
- 同步消除
变量读写不存在竞争,变量的同步措施可以消除
- 标量替换
把一个JAVA对象拆散,将其使用到的成员变量回复原始类型来访问。
缺点:
不能保证逃逸分析的性能收益必定高于他的消耗。