有同学问我说:
问个问题~~ 我们HotSpot,用jstat看JIT有很高的failed的比例。有没有什么文档讲为什么JIT compilation会fail,还有那个failed type到底是什么意思,以及identify哪些method有compilation failure的啊?我以前看到的那些书完全没有讲到这些啊(
难道这jstat说的failure,其实就是deoptimization的意思么…
嗯。很简单的问题,来回答一下。
这是关于Oracle/Sun JDK / OpenJDK自带的jstat工具的问题。其实我以前的blog上写过如何自己查找jstat的各种数据的含义,请跳这俩传送门:
- 用Java获取full GC的次数?(2) - Script Ahead, Code Behind
- 通过jstat工具来查看jvmstat monitor的值 - Script Ahead, Code Behind
jstat -compiler的一个示例输出:
$ jstat -compiler 55417
Compiled Failed Invalid Time FailedType FailedMethod
6296 6 0 50.37 1 org/eclipse/jdt/internal/core/CompilationUnitStructureRequestor createTypeInfo其中所使用的数据,由这个配置所定义:
8c93eb3fa1c0 src/share/classes/sun/tools/jstat/resources/jstat_options
option compiler {
column {
header "^Compiled^" /* Number of compilation tasks performed */
data sun.ci.totalCompiles
scale raw
align right
width 6
format "0"
}
column {
header "^Failed^" /* Number of failed compilation tasks */
data sun.ci.totalBailouts
scale raw
align right
width 6
format "0"
}
column {
header "^Invalid^" /* Number of invalidated compilation tasks */
data sun.ci.totalInvalidates
scale raw
align right
width 6
format "0"
}
column {
header "^Time^" /* Time spent in compilation */
data java.ci.totalTime/sun.os.hrt.frequency
align right
scale sec
width 8
format "0.00"
}
column {
header "^FailedType^" /* Type of last failed compilation */
data sun.ci.lastFailedType
scale raw
align right
width 4
}
column {
header "^FailedMethod" /* Name of class and method for last failed compile */
data sun.ci.lastFailedMethod
scale raw
align left
width 1
}
}
想知道这些列是什么意思,看看这个配置文件就知道。
它们都是通过HotSpot VM所支持的jvmstat API获取HotSpot VM内部的PerfCounter计数器的值,而相应计数器的名字就由配置文件里的data那项所指定。 有兴趣的同学可以在HotSpot VM的源码里搜搜下面的PerfCounter的名字,就可以知道在什么样的情况下这些计数器会被更新,于是就可以很清楚地知道计数器的含义。
- Compiled:一共进行了多少次编译任务。一次“编译任务”(CompileTask)对应一个顶层方法的编译;一个Java方法可能出于不同原因会多次被JIT编译。
- 计数器名:sun.ci.totalCompiles
- PerfCounter:perf_total_compile_count
- Failed:在执行了的编译任务中,有多少个编译失败了。JIT编译器可能会出于一些限制条件,或者是未修复的bug,而决定在编译过程中中止该编译任务。这种行为叫做编译器的bailout。举例来说,C2在编译过程中如果发现被编译的方法经过优化后有无限空循环的话,就会决定中止编译并bailout,这属于限制条件;C2在做了某些优化后发现IR的形状不对了,为了避免bug而决定中止编译并bailout,这属于规避未修复的bug。注意:这并不是deoptimization。
- 计数器名:sun.ci.totalBailouts
- PerfCounter:perf_total_bailout_count
- Invalid:在已经执行的编译任务中,有多少在编译完成后未能被安装到CodeCache中。这通常是由于HotSpot VM的JIT编译是后台并发进行的,在编译过程中Java程序还可能边执行边触发新的类加载,而新的类加载可能会导致正在进行的JIT编译在刚开始所做的激进假设到编译完成的时候已经不成立了(例如说某个编译任务在JIT编译刚开始的时候假设抽象基类Base只有一个具体派生类Foo,但一边编译一边有新的Base派生类Bar给加载了进来)。
- 计数器名:sun.ci.totalInvalidates
- PerfCounter:perf_total_invalidated_count
- FailedType:最近一个编译失败的编译任务的失败原因代号。这个下面说。
- 计数器名:sun.ci.lastFailedType
- PerfCounter:perf_last_failed_type
- FailedMethod:最近一个编译失败的编译任务是哪个Java方法。这个顾名思义啦。
- 计数器名:sun.ci.lastFailedMethod
- PerfCounter:perf_last_failed_method
关于那个FailedType列里的代号都是什么意思,请参考:
// Compile type Information for print_last_compile() and CompilerCounters
enum { no_compile, normal_compile, osr_compile, native_compile };
所以:
- 0:no_compile:没在编译
- 1:normal_compile:普通编译(从方法正常入口开始编译)
- 2:osr_compile:On-Stack Rreplacement编译(从方法中某个循环的回边开始编译)
- 3:native_compile:native wrapper的编译
就这样。
专栏:编程语言与高级语言虚拟机杂谈(仮)
探讨编程语言的设计与实现