程序编译与代码优化

解释器与编译器

  • 解释器:对代码(字节码)解释执行,执行效率较低但可以省去编译的时间,快速启动程序。
  • 编译器:将代码编译成本地代码后执行,执行效率高,但需要花费时间在编译上。

Hotspot VM内的解释器与编译器

  1. 解释器(Interpreter)
  2. C1即时编译器(Client JIT Compiler)
  3. C2即时编译器(Server JIT Compiler)

分层编译

  • 第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译。
  • 第1层:也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑。
  • 第2层:也称为C2编译,也是将字节码编译为本地代码,但是会启用一些耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

热点探测

热点代码

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

热点探测方式

  • 基于采样的热点探测:定期检查各线程的栈顶,统计出现在栈顶的方法,出现次数较多的认为是热点方法。优点:实现简单,高效,容易获取方法调用关系。缺点:难以精确统计方法的热度,易受线程阻塞或外界因素影响。
  • 基于计数器的热点探测:为方法或代码块建立计数器,统计方法执行的次数,执行次数超过一定阈值则认为是热点方法。优点:统计结果精确严谨。缺点:实现复杂,难以获取方法的调用关系。

Hotspot VM基于计数器的热点探测实现

  • 热点方法:方法调用计数器用于统计方法被调用的次数,当计数器的值超过阈值时,那么将会向即时编译器提交一个该方法的代码编译请求,而后方法将被继续解析执行,直到编译完成,系统会自动改写方法的调用入口地址。

    • 热度衰减及半衰周期:在一定时间限度后,如果方法的调用次数仍然未达到阈值,则这个方法的调用计数会被减少一半,这称为方法调用计数器的热度衰减,这个时间称为方法的半衰周期。
  • 热点循环:回边计数器用于统计一个方法中循环体代码的执行次数,当计数器的值超过阈值时,将会提交栈上替换请求,同样循环将被继续解析执行,直到编译完成。回边计数器没有热度衰减。

编译优化技术

  • 公共子表达式消除:如果一个表达式E已经计算过了,并且从先前的计算到现在E中所有变量的值都没有发生变化,那么E的这次出现就称为了公共子表达式,可使用之前计算的值直接代替。
  • 数组边界检查消除:在编译期根据数据流分析确定数组访问不会超出数组的长度范围时,可以将数组上下界检查消除。
  • 方法内联:一个方法被多次调用时,可以将方法体替换到调用处,从而节省方法调用的开销并可为进一步优化建立良好的基础。
  • 类型继承关系分析:用于确定在目前已加载的类中,某个接口是否有多于一种的实现,某个类是否存在子类,子类是否为抽象类等信息。
  • 逃逸分析:分析对象动态作用域,如对象是否被作为参数传递到其他方法中,称为方法逃逸;是否能被其他线程访问到,称为线程逃逸。

    • 栈上分配:对于确定不会出现方法逃逸的对象,可以将其在栈上进行分配,那么对象将随着方法退出而自动销毁,降低垃圾收集的压力。
    • 同步消除:对于确定不会出现线程逃逸的对象,可以消除其同步措施,节省同步的耗时。
    • 标量替换:如果一个对象不会被外部访问,并且可以被拆散为若干标量的话,那么对象可能不会被真正创建,而是改为创建它的若干个被使用到的成员变量来代替,这些成员变量可能会被创建在栈上。