首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

IBM JVM for Linux JIT 诊断简介(3)

IBM JVM for Linux JIT 诊断简介(3)

如何发现失败的方法?设置 JITC_COMPILEOPT=COMPILING,并再次运行您的程序。在设置这个选项之后,JIT 编译器会在对方法进行编译时为每个方法打印两条消息。例如:
1
2
Compiling {java/lang/MyClass}{MyMethod}
0x0BF025C0 {java/lang/MyClass}{MyMethod}




第一条消息 Compiling {java/lang/MyClass}{MyMethod} 说明 JIT 编译器已经开始编译这个方法了。第二条消息说明编译过程已经完成了,十六进制的地址表示每次调用这个方法时,所执行的机器代码的起始偏移量。
如果您看到如下格式的一条消息,其中“F”表示这个方法没有被编译成功。
1
Compiling   F   {package/class}{method}




如果您怀疑会失败的方法的名字右边有一个“X”,那么这个“X”就表示发生了一次 MMI2JIT 转换,其中在检测到循环时对这个方法进行了编译,在一次调用这个方法时,对该方法的执行方式从解释(MMI)模式切换到了编译(JIT)模式。例如:
1
Compiling {java/util/MyClasss}{MyMethod} X




有三种可能的情景可以查找除程序输出之外的最后一条消息:
  • 0x012345678 {package/class}{method}:崩溃是在执行一个已编译的方法(不一定是这条消息列出的方法)时发生的 。
  • 该程序继续运行,但是性能降低了,或者需要花费更多时间来判断崩溃之前哪一条是最后的消息了。
  • Compiling {package/class}{method}:崩溃是在编译过程中发生的。
在第一种情况下,可能的原因是 JIT 编译器会为一个方法产生已编译的代码,这个已编译的代码会产生一个错误。您也许可以通过检查错误发生处的地址来判断是哪个方法导致了这个错误的发生。
如果您可以获得一个错误指令地址,请遵循以下步骤来隔离失效的方法:
  • 将 COMPILING 选项修改为 COMPILING(verbose)。这将会导致在输出结果中包含每个方法已编译的代码的起始地址和结束地址。如果您已经将 COMPILING 选项的范围缩小到一个类/方法规范中,请使用 COMPILING(verbose){class-spec}{method-spec}。
  • 再次运行这个程序,但是要将标准输出重定向到一个文件中,例如:
    1
    java -options classname >compiling.log




  • 当错误发生时,使用 gdb 调试器来寻找错误地址,这取决于您的操作系统(您需要重复这次调用过程,因为两次调用时失败的地址可能会不同)。
    • 在产生 core 文件的目录中启动 gdb 调试器:
      1
      gdb java core




    • 如果 gdb 调试器可以读取 core 文件,它就可以分析出失效发生的地址(例如,Segmentation fault in function_name at 0x10001234)。
    • 要获得一个堆栈跟踪信息,可以使用 bt 命令:
      1
      (gdb) bt




    • 然后使用 quit 命令退出 gdb。
    确保 core 文件的大小(这可以在 ulimit -c 命令的结果中看到)没有被设置为 0 或设置得太小,因为在前一种情况下,不会产生 core 文件,而在后一种情况下,core 文件可能会被截断。
  • 在 compiling.log 文件中查找一个函数的开始地址和结束地址,它是十六进制的形式,其间就包含着错误地址。注意开始的十六进制地址通常是以升序排列的,但这并不是必须的。
  • 如果您找到一个函数地址范围包含了失败的指令地址,那么这个范围的消息就会说明可能引入错误的方法。在第二种情况下,错误指令是未知的,因此需要使用其他技术来缩小出错类/方法对的范围:
    • 如果在程序崩溃之前打印(或者写到 javacore.txt)了 Java 堆栈跟踪,就检查它。在堆栈跟踪信息顶部列出的方法就是主要的候选者。
    • 如果您可以访问源代码,请在所怀疑的 Java 方法中插入一些指示性的代码,注意哪些怀疑的代码没有启动,或者没有完成。在有些情况下,在所怀疑的方法中添加一个 System.out.println() 语句可能会导致问题的消失,因为字节码和最终为该方法编译后的代码可能会发生很多变化,从而改变对该方法所应用的优化流程。在这种情况下,把出现 println() 的方法看作被怀疑的方法。如果您在很多怀疑的方法中都插入一个 println() 调用,则尽量删除 println() 调用,直到您确定哪一个防止了失败的出现;包含 println() 调用的方法很可能就是值得怀疑的地方。
    • 使用 libjitc.so 的调试版本 —— libjitc_g.so。
调试版本的 JIT 库包含对 C 库函数中的 assert() 函数的调用。这些调用被放到 JIT 编译器的代码中,用来断言所需要的条件为真;当一个 assert 调用失败时,就说明 JIT 编译器代码碰到了非预期的情况。通常一个断言失效都是在编译一个失败方法时发生的,然而在使用非调试版本的 JIT 库时,失败情况可能要到编译方法中的一个特定指令在以后实际执行时才会出现。当使用调试版本的 JIT 库时,您的程序会更可能在打印问题方法的 Compiling 消息之后失败。
调试版本的可执行程序和共享库包含在 JDK 中,但没有包含在 JRE 中。
要使用调试版本的 JIT 库,请遵循以下步骤:
  • 检查在 java 命令的  -Dsun.boot.library.path  选项中所指向的一个目录中存在所需要的调试库。检查存在共享库 libjitc_g.so。
  • 在调用 java 命令之前,设置环境变量:JAVA_COMPILER=jitcg。
如果您看到下面的消息:
1
Warning: JIT compiler "jitcg" not found. Will use interpreter





请检查所指定的库路径(-Dsun.boot.library.path)是正确的,所指定的调试库在这个路径中的一个目录中的确存在。
当您运行这个版本的 JVM 时,如果碰到断言失败的情况,执行过程就会停止,同时会显示一条断言失败消息。
在第三种情况下,或者在方法编译失败时,您会看到:
1
Compiling   F   {package/class}{method}





您可以跳过有问题的方法,不对其使用 JIT 进行编译。
您可以使用 JITC_COMPILEOPT 变量的 SKIP 选项禁用对失败方法进行 JIT 编译。
例如:
JITC_COMPILEOPT=SKIP{failingPackage/failingClass}{failingMethod}跳过对特定类中的特定方法进行 JIT 编译的过程JITC_COMPILEOPT=SKIP{failingPackage/failingClass}{*}跳过对一个类的所有方法进行 JIT 编译的过程JITC_COMPILEOPT=SKIP{failingPackage/*}{*}这可以跳过对失败包中所有类的所有方法进行 JIT 编译的过程如果上面其他查找失效方法的技术都失败了,那么这种相同的跳过一些方法的技术可以应用到更广泛的范围,作为一种强制方法来隔离失败的方法,或者一个失败方法的集合。
JITC_COMPILEOPT 变量在 Linux 以及其他平台上允许使用多个以点号(.)分隔开的选项,以及在正则表达式中使用通配符。例如:
JITC_COMPILEOPT=COMPILING:SKIP{com/class1/*}{*}{com /class2/*}{*}跳过 JIT 对 firm.empdata 和 ourfirm.orderentry 包中所有类的所有方法的编译,按照“COMPILING”选项的定义打印 JIT 编译的信息。JITC_COMPILEOPT=SKIP{myClass?2}{myfunc?b} 跳过名为 myClass?2 的任何类中的名为 myfunc?b 的所有方法,其中 ? 是任意的单个字符。JITC_COMPILEOPT=SKIP{[ab]?[de]*}{*} 跳过第一个字母为“a”或“b”,第三个字母为“d”或“e”的所有类中的所有方法。 布尔 OR 和 NOT 操作分别是 | 和 ^ 操作符。注意这些字符在某些命令 shell 中具有特殊的意义,需要使用转义字符或引号。
JITC_COMPILEOPT=SKIP{java*|sun*}{*} 跳过以 java 或 sun 开始的所有类的所有方法。JITC_COMPILEOPT=SKIP{^[js]*}{*}跳过除了以“j”和“s”开头的所有类的所有方法。在这个过程的最后,您应该可以提炼一组范围有限的方法集合,或者理想地说,甚至是提炼出一个方法,这样在为这个方法或这个范围很小的方法集合禁用所有的 JIT 优化选项时可以解决问题。
如何寻找有问题的 JIT 优化选项?在上一节的最后,您应该早已可以确定跳过所选择的方法、类或包防止了问题的出现。在本节中,您将找到有问题的 JIT 优化选项,在对上一节中确定的方法集合或单个方法执行时,这些优化选项就会产生问题。如果您遵循 3.2 节中介绍的快速解决方案的步骤,通过寻找一个高级的优化选项集合进行禁用从而绕过问题来寻找一个解决方案,那么您可能早已确定了一个高级的优化选项集合。首先,您可以将 SKIP 选项修改为 NALL 选项,从而判断基本的编译是否是问题的根源,或者是否是由于优化选项而引起的问题。NALL 可以禁用大部分优化选项,但是没有禁用基本的编译。
如果您的 SKIP 选项包含了多个类规范/方法规范对,那么您就可能会希望一次对一个集合从 SKIP 切换到 NALL 上。尽管这会使得解决问题需要花费很多时间,但会给您一个目标更明确的解决方案集,在将来您请求技术支持时,使 IBM 更容易修正 JIT 的问题,在您等待修正程序的问题时,可以为每个类都提供最优的性能。
例如,如果您将失败的方法范围限定到:
1
2
SKIP{MyPackage/MyClass}{MyMethod}
{MyPackage2/MyClass2}{MyMethod2}




那么您就可以使用 SKIP 和 NALL 选项,如下所示:
1
2
export JITC_COMPILEOPT=NALL{MyPackage/MyClass}{MyMethod}:
SKIP {MyPackage2/MyClass2}{MyMethod2}




注意上面这行只是为了格式化文档的需要。在设置 JITC_COMPILEOPT 时应该没有空格和换行。
再次运行一下这个程序。如果它可以成功运行,那么您需要提炼优化选项。您可能需要遵循 3.2 中的步骤来找到一个快速解决方案,它会逐一禁用大部分高级的优化选项。这可以通过深入特定的优化层次进行深入的提炼。
如果这个程序不能运行,那么就可能存在本文中讨论的其他原因。您可以通过对所选择的方法跳过整个 JIT 编译过程来寻找一种解决方案。
现在,您可以联系 IBM 的技术服务部门了。
合并结果调试过程的最终结果应该是对一组范围很小的方法禁用一组范围很小的优化选项。在最简单的情况下,只对一个方法执行了一个有问题的优化选项。
例如,如果您在对 MyPackage/MyClass 的 myMethod 方法进行内联时确定了有问题的行,那么最终的解决方案形式如下(Korn Shell):
1
export JITC_COMPILEOPT=NINLINING{MyPackage/MyClass}{myMethod}




针对 Linux 的当前 IBM JVM 产品在撰写本文时,Linux 上的 JDK 和 JRE 可以从 下载,地址为

结束语本文简要介绍了 IBM JVM 中使用的优化技术:JIT 和 MMI。
本文中介绍的简要 JIT 诊断过程可以帮助:
  • 确认 JIT 是问题的根源。
  • 将系统快速恢复到生产可接受的性能。
  • 在将 Java 程序从非基于 JIT 的 JVM 迁移到 IBM JVM 的过程中,如果碰到与 JIT 有关的问题,请搜集 IBM 修正问题的根源所需要的信息。
返回列表