晚期(运行期)优化

解释器与编译器

Java在运行的时候,他的解释器和编译器会同时工作,解释器解释运行代码,编译器有选择性地编译部分代码为机器代码,以加速Java代码的执行过程

编译器

Java编译器有两种,一种是客户端编译器,他进行简单的可靠的优化,并适时加入性能检测的逻辑,另一种是服务器编译器,他进行耗时较长的优化,甚至根据性能检测信息,加入一些不可靠的激进的优化

编译器编译的对象

  • 被多次调用的方法
  • 被多次执行的循环体

方法

  • 基于采样的热点探测:虚拟机周期性的检查各个线程的栈顶,如果发现某个方法经常出现在栈顶,那这就是一个热点方法,优点是简单高效,缺点是容易受到线程阻塞等其他因素的影响。
  • 基于计数器的热点探测:为每一个方法添加一个计数器,每当方法被调用,则计数器增大1,每经过一定时间(可以与gc相关)就让计数器变为原来的一半,这是一种和式增加,积式减少的策略,这个在计算机网络中的滑动窗口大小控制也有应用,当计数器超过某个阈值的时候,就让编译器来把这个方法编译成机器码即可。

循环体

和上文的计数器热点探测相似,但计数器永远不会变小。若超过一个阈值,整个方法都会被编译成机器码

编译优化

各语言通用优化

内联、冗余访问清除、复写传播、无用代码清除、公共子表达式消除

Java编译器优化

  • 隐式异常优化: 使用Segment Fault信号的异常处理器代替数组越界异常、空指针异常、除0异常等
  • 方法内联: 由于Java基本都是虚函数,这导致方法内联不太容易实现,对于非虚函数,直接内联,对于虚函数,CHA(类型继承关系分析)会检测,当前程序的这个方法是否对应了多个版本,若没有,则进行激进优化,强行内联并预留一个逃生门,以便在新类加载的时候,抛弃此代码,使用解释器,如果CHA查出有多个版本,也会为进行一个缓存处理,保留上一次的信息,若下一次进来的版本相同,则内联可以继续使用,否则就只能回到解释器了。

逃逸分析

逃逸分析是一种分析技术,而不是直接优化代码的手段。

栈上分配

如果分析出一个对象不会被他被定义的方法以外的方法用到,那个这个对象会被分配到栈上。

同步消除

如果分析出一个对象不会被他被定义的所在线程以外的线程用到,那么这个对象的同步指令会被清除。

标量替换

如果分析出一个对象不会被外部访问,他甚至会被拆成多个基本数据类型,分配到栈上,甚至被虚拟机直接分配到物理机的高速寄存器中