相比于其他JVM,JRockit的设计颇为不同,它不鼓励过多的使用命令行参数,即使要改变代码生成和优化策略也不例外。本节的内容以介绍说明为主,读者在使用相关命令行参数时应小心可能产生的副作用。
## 2.7.1 命令行选项与指导文件本节的内容主要适用于JRockit R28及其后的版本。如果读者使用的是早期的版本,那么请查询相关文档以确定于本节内容相对应的部分。注意,R28版本的部分功能可能与之前的版本略有不同。
在JRockit很少会因代码生成器故障而导致出现问题,应用程序行为异常,或是花费很长时间去优化某个方法,这是因为代码生成器的行为是可控的,读者可以根据具体需求而修改代码生成器的行为,当然,这样的前提是,读者需要清楚的知道自己在做什么。
### 2.7.1.1 命令行选项JRockit支持使用命令行选项对代码生成器的行为做粗粒度的控制。
选项-Xverbose:codegen
(或者-xverbose:opt
)用于让JRockit在每次执行JIT编译(或优化)时向标准错误(即stderr
)打印两行相关信息。
以常见的HelloWorld
程序为例,每次生成代码时都会打印两行日志,在开始和结束时各一行。
hastur:material marcus$ java –Xverbose:codegen HelloWorld
[INFO ][codegen][00004] #1 (Normal) jrockit/vm/RNI.transitToJava(I)V
[INFO ][codegen][00004] #1 0.027-0.027 0x9e5c0000-0x9e5c0023 0.14 ms (0.00 ms)
[INFO ][codegen][00004] #2 (Normal)jrockit/vm/RNI.transitToJavaFromDbgEvent(I)V
[INFO ][codegen][00004] #2 0.027-0.027 0x9e5c0040-0x9e5c0063 0.03 ms (0.00 ms)
[INFO ][codegen][00004] #3 (Normal) jrockit/vm/RNI.debuggerEvent()V
[INFO ][codegen][00004] #3 0.027-0.027 0x9e5c0080-0x9e5c0131 0.40 ms 64KB 0 bc/s (0.40 ms 0 bc/s)
[INFO ][codegen][00004] #4 (Normal)jrockit/vm/ExceptionHandler.enterExceptionHandler()Ljava/lang/Throwable;
[INFO ][codegen][00004] #4 0.027-0.028 0x9e5c0140-0x9e5c01ff 0.34 ms 64KB 0 bc/s (0.74 ms 0 bc/s)
[INFO ][codegen][00004] #5 (Normal)jrockit/vm/ExceptionHandler.gotoHandler()V
[INFO ][codegen][00004] #5 0.028-0.028 0x9e5c0200-0x9e5c025c 0.02 ms (0.74 ms)
...
[INFO ][codegen][00044] #1149 (Normal) java/lang/Shutdown.runHooks()V
[INFO ][codegen][00044] #1149 0.347-0.348 0x9e3b4040-0x9e3b4106 0.26 ms 128KB 219584 bc/s (270.77 ms 215775 bc/s)
hastur:material marcus$
这两行日志中的第一行包含以下信息:
- Info标签和日志模块标识符 (代码生成器)
- 当前生成代码所使用的线程的id: 依赖于具体的系统配置,这里可能会有多个代码生成线程和代码优化线程
- 生成的代码的索引值: 第一个生成的代码的索引值是1。值得注意的是,在整个日志的开始部分,生成代码只使用了一个线程,因此代码生成日志中开始和结束日志的顺序还是正常的,是连续的,但当使用了多个线程来处理代码时就有可能会出现开始和结束日志交错的现象。
- 代码生成策略: 代码生成策略指明了该方法的生成方式。由于在之前的运行阶段已经收集到了一些与该方法相关的信息,所以在生成代码的时候,或者是普通生成策略,或是粗略的生成一个立等可用的。粗略生成的方法往往是运行时性能没什么影响的,例如像静态初始化方法这种只会运行一次的方法,因此,花费大力气为其做寄存器分配是毫无意义的。
- 待生成的方法: 具体内容包括类名、方法名和方法描述符。
日志的第二行包括以下信息
- Info标签和日志模块标识符 (代码生成器)
- 当前生成代码所使用的线程的id
- 生成的代码的索引值
- 代码生成事件的开始和结束时间: 该事件是自JVM启动之后的时间偏移值,单位是秒。
- 地址范围: 这是生成的本地代码要放置的内存空间。
- 代码生成时间: 根据该方法生成对应的本地代码所花费的时间(这里指的是从字节码开始),单位是毫秒。
- 生成代码所消耗的线程局部内存的最大值: 这个值是代码生成线程为编译代码所分配的内存空间的最大值。
- 每秒处理的字节码数量的平均值: 这个值是编译该方法时每秒编译的字节码数量的平均值。注意,0表示无穷大,这是因为精度不足。
- 生成代码所花费的总时间: 这部分内容包括2块,分别是自JVM启动后,该线程在代码生成上所花费的总时间(单位是毫秒)和每秒编译的字节码数量的平均值。
命令行选项-XnoOpt
和–XX:DisableOptsAfter=<time>
用于关闭优化编译器的所有优化操作,区别在于选项–XX:DisableOptsAfter=<time>
可以指定在JVM启动多少秒之后再禁用优化。应用选项-XnoOpt
可以加快应用程序的编译速度,但运行效率会降低。如果怀疑是由于JRockit优化编译器而导致某个问题的出现,或者优化编译的时间很长,则可以应用-XnoOpt
选项来一探究竟。
改变JVM编译代码所用的线程数在某种程度上可以使应用程序运行的更好,但实际情况与具体的机器配置有关。除了将本地代码放入到代码缓冲区和类载入的某些步骤之外,代码的生成和优化是可以并行进行的。应用命令行选项-XX:JITThreads=<n>
可以指定JIT编译器线程的数量,选项-XX:OptThreads=<n>
可以指定优化线程的数量。注意,优化操作需要大量的内存和CPU资源,即使机器中CPU的核很多,也不要将优化线程设置得过多。
要想细粒度的控制JRockit的代码生成,可以使用 指导文件(directive file)。在指导文件中,可以使用通配符来表示多个目标方法,并指明代码生成器的具体行为。事实上,可以指定的行为非常多,本节重在介绍指导文件的相关概念,因此不会详述所有的行为。
警告:JRockit并未对指导文件提供完整支持,也没有发布过正式的说明文档,将来也有可能会对其内容进行修改。Oracle也不会提供使用指导文件控制JRockit配置的功能。
命令行选项-XX:OptFile=<filename>
用于指定要使用的指导文件,此外,也可以在JVM运行过程中,使用 JRCMD或JRockit JavaAPI来添加/移除指导文件(关于JRCMD和JRockit Java API的内容会在本书的后续章节中介绍)。不过,要想使用指导文件,还需要应用选项-XX:+UnlockDiagnosticVMOptions
,该选项会启用一些用于诊断JVM的命令行选项,这些选项在将来的JVM版本中可能会修改,直接使用有风险。
其实,指导文件就是以 **JSON(JavaScript Object Notation)**格式编写的指令集合,如下所示
{
//pattern to match against class + method + signature
match: "java.dingo.Dango.*",
enable: jit
}
在上面的例子中,禁用了对java.dingo.Dango
类中所有方法的优化,只保留的JIT编译,这是因为在enable
字段的值只有jit
,而没有指定hotspot
。
如果想要在第一次代码生成时强制执行优化,可以使用如下的配置:
{
match: "java.dingo.Dango.*",
//types of "reasons" for codegen we allow
enable: jit,
jit: {
preset : opt
}
}
上面配置的意思是,java.dingo.Dango
类中所有的方法只能执行JIT编译,但在编译的时候要应用预设的优化策略。JRcokit中包含有一些预设的优化策略,其中opt
的意思是 立即全面优化该方法。
相比于运行一段时间之后,由运行时根据收集到的信息对该方法进行优化,通过使用
opt
选项在系统第一次编译方法时就做全面优化的效果未必更好,所生成的本地代码可能有所差别,性能也不尽相同。过早的对方法进行优化,会因为收集到的运行时信息不足而难以做到位。事实上,运行时可能已经将这个方法放到了优化队列中,只不过还开未行而已。
代码生成策略可以用更精细的方法加以覆盖,例如向下面的示例一样单独关闭某个方法的优化,或禁止对某个方法做内联操作。
//Using more than one directive, should use an array '['.
[
//directive 1
{
match: "java.dingo.Dango.*",
enable: [ jit, hotspot ], //allow both jit and optimization
hotspot: {
fusion_regalloc : false; //forbid graph fusion for opt
},
jit_inline : false, //forbid jit inlining
},
//directive 2
{
match: [ "java.lang.*", "com.sun.*" ],
enable: jit ,
jit: {
//copy the opt preset, i.e. force optimization
//for jit, but disable inlining
preset : opt,
opt_inline : false,
},
},
//directive 3
{
match: "com.oracle.*",
//force optimizer to always inline java.util methods
//force optimizer to NEVER inline com.sun.methods
inline: [ "+java.util.*", "-com.sun.*" ],
}
]
在JRcokit中,指导文件是对编译和优化详细配置,只不过没有相关的说明文档,它是追踪问题的好帮手,但千万不要滥用。
建议在使用指导文件时附加-Xverbose:opt
选项以确保JVM读取并应用了该文件。