[Java][HotSpot VM] jstat -compiler的输出的含义

有同学问我说:
问个问题~~ 我们HotSpot,用jstat看JIT有很高的failed的比例。有没有什么文档讲为什么JIT compilation会fail,还有那个failed type到底是什么意思,以及identify哪些method有compilation failure的啊?我以前看到的那些书完全没有讲到这些啊(
难道这jstat说的failure,其实就是deoptimization的意思么…

嗯。很简单的问题,来回答一下。

这是关于Oracle/Sun JDK / OpenJDK自带的jstat工具的问题。其实我以前的blog上写过如何自己查找jstat的各种数据的含义,请跳这俩传送门:

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的编译

就这样。

7 条评论