Java程序最初是通过解释器进行解释执行的,当某段代码(称为“热点代码”)运行特别频繁,为了提高效率,在运行时,虚拟机通过即时编译器(JIT)把这些代码编译成与本地平台相关的机器码。
大纲
概述
即时编译器编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机优秀与否的最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。
解释器与编译器(HotSpot)
主流的商用虚拟机,都同时包含解释器与编译器,两者配合工作。
编译对象与触发条件
编译对象(热点代码)
- 被多次调用的方法
- 被多次执行的循环体
触发条件(热点探测)
- 基于采样的热点探测
采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某个(或某些)方法经常出现在栈顶,那这个方法就是“热点方法”。
优点:实现简单高效、容易获取方法调用关系
缺点:很难精确地确定一个方法的热度,容易受线程阻塞影响 - 基于计数器的热点探测(HotSpot虚拟机使用)
采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。
优点:统计结果相对更加精确和严谨
缺点:实现麻烦,需要为每个方法建立并维护计数器- 计数器
- 方法调用计数器
- 回边计数器
- 方法调用计数器
- 计数器
编译过程
- Client Compile编译器
简单快速的三段式编译器,主要的关注点在于局部性的优化,而放弃了许多耗时较长的全局优化手段。 - Server Compiler编译器
专门面向服务端的经典应用并为服务端的性能配置特别调整过的编译器,会执行所有经典的优化动作、实施一些与Java语言特性密切相关的优化技术,还可能进行一些不稳定的激化优化。相对来说执行比较慢,但代码质量高,减少本地diamante的执行时间。
编译优化技术
以编译方式执行本地代码比解释方式更快,主要有两个原因:
- 解释执行字节码需要额外消耗时间
- 虚拟机设计团队几乎把对代码的所有优化措施都集中在了即时编译器之中
语言无关的经典优化技术
- 公共子表达式消除
- 局部公共子表达式消除:优化仅限于程序的基本块内
- 全局公共子表达式消除:优化的范围涵盖了多个基本块
- 数组范围检查消除
- 方法内联
- 消除方法调用的成本
- 为其他优化手段建立良好的基础
- 逃逸分析
如果确定一个对象不会逃逸出方法之外,让该对象在栈上分配内存,对象所占用的内存空间就可以随栈帧出栈而销毁。- 方法逃逸:当一个对象在方法中被定义后,它可能被外部方法所引用
- 线程逃逸:被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量
Java与C/C++的编译器对比
Java虚拟机可以因为下列原因导致输出的本地代码有一些劣势,虽然存在这些性能的劣势,但为了换取开发效率上的优势,也算值得。
- 因为即时编译器运行占用的是用户程序的运行时间,具有很大的时间压力,它能提供的优化手段也严重受制于编译成本
- Java语言是动态的类型安全语言,这就意味着需要由虚拟机来确保程序不会违反语言语义或访问非结构化内存
- Java语言中虽然没有virtual关键字,但是使用虚方法的频率却远远大于C/C++语言
- Java语言是可以动态扩展的语言,运行时加载新的类可能改变程序类型的继承关系
- Java语言中对象的内存分配都是堆上进行的,只有方法中的局部变量才能在栈上分配