【JVM】关于JVM,你需要掌握这些 | 一文彻底吃透JVM系列( 七 )


分层编译器有哪些层次?分层编译根据编译器编译、优化的规模和耗时 , 划分不同的编译层次 , 包括:

  • 第0层:程序解释执行 , 解释器不开启性能监控功能 , 可出发第1层编译 。
  • 第1层:也成为C1编译 , 将字节码编译为本地代码 , 进行简单可靠的优化 , 如有必要加入性能监控的逻辑 。
  • 第2层:也成为C2编译 , 也是将字节码编译为本地代码 , 但是会启用一些编译耗时较长的优化 , 甚至会根据性能监控信息进行一些不可靠的激进优化 。
用Client Compiler和Server Compiler将会同时工作 。用Client Compiler获取更高的编译速度 , 用Server Compiler获取更好的编译质量 。
编译对象与触发条件热点代码有哪些?
  • 被多次调用的方法
  • 被多次执行的循环体
如何判断一段代码是不是热点代码?要知道一段代码是不是热点代码 , 是不是需要触发即时编译 , 这个行为称为热点探测 。主要有两种方法:
  • 基于采样的热点探测 , 虚拟机周期性检查各个线程的栈顶 , 如果发现某个方法经常出现在栈顶 , 那这个方法就是“热点方法” 。实现简单高效 , 但是很难精确确认一个方法的热度 。
  • 基于计数器的热点探测 , 虚拟机会为每个方法建立计数器 , 统计方法的执行次数 , 如果执行次数超过一定的阈值 , 就认为它是热点方法 。
HotSpot虚拟机使用第二种 , 有两个计数器:
  • 方法调用计数器
  • 回边计数器(判断循环代码)
方法调用计数器统计方法统计的是一个相对的执行频率 , 即一段时间内方法被调用的次数 。当超过一定的时间限度 , 如果方法的调用次数仍然不足以让它提交给即时编译器编译 , 那这个方法的调用计数器就会被减少一半 , 这个过程称为方法调用计数器的热度衰减 , 这个时间就被称为半衰周期 。
有哪些经典的优化技术(即时编译器)?
  • 语言无关的经典优化技术之一:公共子表达式消除
  • 语言相关的经典优化技术之一:数组范围检查消除
  • 最重要的优化技术之一:方法内联
  • 最前沿的优化技术之一:逃逸分析
公共子表达式消除普遍应用于各种编译器的经典优化技术 , 它的含义是:
如果一个表达式E已经被计算过了 , 并且从先前的计算到现在E中所有变量的值都没有发生变化 , 那么E的这次出现就成了公共子表达式 。没有必要重新计算 , 直接用结果代替E就可以了 。
数组边界检查消除因为Java会自动检查数组越界 , 每次数组元素的读写都带有一次隐含的条件判定操作 , 对于拥有大量数组访问的程序代码 , 这无疑是一种性能负担 。
如果数组访问发生在循环之中 , 并且使用循环变量来进行数组访问 , 如果编译器只要通过数据流分析就可以判定循环变量的取值范围永远在数组区间内 , 那么整个循环中就可以把数组的上下界检查消除掉 , 可以节省很多次的条件判断操作 。
方法内联内联消除了方法调用的成本 , 还为其他优化手段建立良好的基础 。
编译器在进行内联时 , 如果是非虚方法 , 那么直接内联 。如果遇到虚方法 , 则会查询当前程序下是否有多个目标版本可供选择 , 如果查询结果只有一个版本 , 那么也可以内联 , 不过这种内联属于激进优化 , 需要预留一个逃生门(Guard条件不成立时的Slow Path) , 称为守护内联 。

经验总结扩展阅读