JAVA虚拟机学习总结——运行期优化

JAVA虚拟机的运行期优化

解释器与编译器

java 程序最初就是通过解释器进行解释执行的。当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。为了提高热点代码的运行效率,在运行时,虚拟机会把这些代码编译成于本地平台相关的机器码,并进行各个层次的优化,完成这个任务的编译器称为即时编译器。

解释器与编译器共存

  • 在程序启动的时候,解释器可以首先发挥作用,省去编译时间立即执行。在
    程序运行时,编译器逐渐发挥作用,把越来越多的代码编译成本地代码,获取更高的运行效率。
  • 解释执行节约内存,编译执行提高效率。
  • 解释器作为编译器激进优化的一个逃生门。

编译对象与触发条件

热点代码

被多次调用的方法和被多次执行的循环体(编译的都是整个方法).

热点探测方式

基于采样的热点探测

虚拟机周期性地检查各个线程的栈顶,如果发现某个方法经常出现在栈顶,就认定为热点代码。简单高效但是不准确。

基于计数器的热点探测

虚拟机为每个方法建立计数器,统计方法执行次数,如果超过一定阈值就认定为热点方法。

方法调用计数器

统计方法被调用的次数。

回边计数器

统计一个方法中循环体代码的执行次数,在字节码中遇到控制流向后跳转的指令称为回边。

方法调用计数器热度的衰减

方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数,当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程就是方法调用计数器热度的衰减。但是回边计数器就是统计该方法循环体的绝对执行次数。

编译过程

第一阶段

一个平台独立的前端将字节码构成一种高级中间代码表示(HIR)。HIR使用静态单分配的形式代表代码值,这可以使得HIR的构造过程之中或之后进行的优化动作更容易实现。一些基础的优化如方法内联,常量传播在HIR被构成之前完成。

第二阶段

一个平台相关的后端从HIR中产生低级中间代码表示(LIR),在此之前会在HIR基础上完成诸如空值检查消除,范围检查消除的优化。以便让HIR达到更高效的代码表示形式。

第三阶段

在平台相关的后端使用线性扫描算法,在LIR上分配寄存器,并在LIR上做窥孔优化,然后产生机械代码。

几个经典的优化手段

公共子表达式消除

如果一个表达式E已经计算过了,并且先前的计算到现在E中的变量的值都么有发生变化,那么这次E的结果就可以复用上次的结果。

数组边界消除

当访问一个对象极少出现异常的时候,我们可以假设这个对象不会不会出现异常,然后取消对它的异常判断(如不判断是否为空),然后用try.catch进行异常捕获。这时可以节约一次判断时间,但只适合于极少出现异常的情况。

方法内联

消除方法调用的成本,为其他优化手段奠定基础。

  • 直接内联:适合于私有方法,实例构造器,父类方法,静态方法,被final修饰的方法等这些非虚方法。
  • 守护内联:对于非虚方法,如果当前程序只有一个目标版本,也会先进行激进内联。如果程序后续执行过程中没有加载到会令这个方法的接受者的继承关系发生变化的类,那这个内联优化的代码就会一直使用下去,否则就会抛弃已经编译的代码,退回到解释状态执行,或者重新进行编译。
  • 内联缓存:对于非虚方法,如果有多个目标版本,会使用内联缓存进行内联,这是一个建立在目标方法正常入口之前的缓存,它的工作原理大致是:在未发生方法调用之前,内联缓存状态为空,当地一次调用之后,缓存记录下方法接受者的版本信息,并且每次进行方法调用时都比较接受者的版本,如果以后进来的每次调用的方法接受者版本都是一样的,那这个内联还可以一直用下去,如果发生了不一样的情况,说明程序使用了虚方法的多态特性,才会取消内联,查找虚方法表进行分派。
逃逸分析

分析对象的动态作用域。

  • 方法逃逸:一个对象在方法中被定义后,它可能被外部方法所引用,例如作为参数传递到其他方法中。
  • 线程逃逸:一个对象被定义后,可能被外部线程访问到,例如赋值给类变量或可以在其他线程中访问的实例变量。

对于不会有逃逸现象的对象,可以进行如下优化:

  • 栈上分配:将不会有方法逃逸现象的对象分配到栈上,对象所占的空间就随着栈帧出栈而销毁了,减小垃圾收集系统的压力。
  • 同步消除:对不会有线程逃逸现象的对象,消除其同步措施。
  • 标量替换:对于没有逃逸现象,也可以被拆散为标量的对象,程序真正执行的时候可能不会创建这个对象,而是直接创建若干个被这个方法使用到的成员变量来代替,这样可以让这些成员变量分配到栈上,也能为后续优化提供便利。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章