IBM JVM for Linux JIT 诊断简介(1)
- UID
- 1066743
|
IBM JVM for Linux JIT 诊断简介(1)
简介“一次编写,到处运行”(WORA)的原则只有在将纯 Java™ 的字节码从一个平台的某个特定版本的 Java 虚拟机(JVM)移植到另外一个不同平台上完全相同版本的 JVM 上时才适用。
然而,有时这种迁移过程并不是无缝的。可以对这个迁移过程产生影响的一个因素是不同供应商的优化技术的内部实现之间存在差异。
在将一个 Java 程序从使用 Sun JDK 的平台迁移到使用 IBM JDK 的平台上时,重要的是要了解在这两个供应商的 JVM 中使用的优化技术之间的差异,这些差异可能会对程序产生影响,以及如何通过调节 IBM JVM 中可用的优化机制来获得更好的性能。
本文着重介绍对于在 JIT 中碰到的问题的诊断,在从 Sun HotSpot JVM 迁移到 IBM 基于 JIT 的 JVM 时可能会碰到这些问题。
虽然本文中介绍的大部分内容对于 IBM JVM 1.3.1 和 1.4.2 都是普遍适用的,但是尤其适用于 IBM JVM 1.3.1 和 JVM 1.4.2 for Linux,包括 POWER 和 PowerPC 架构上的 Linux。
在 Sun 的 JVM 1.3.1 中,JIT 和 HotSpot 编译器都已经包含其中了,可以使用 -server 或 -hotspot 选项(默认为客户机 HotSpot VM)来调用,使用 -classic 选项调用 JIT,-hotspot 选项是隐含的默认值。在 Sun JDK 1.4.1 及之后的版本中,只能使用 HotSpot 了。IBM JVM 1.3.1 和 1.4.2 使用了 JIT 和 MMI 的组合,这是默认的运行模式,可以实现与 HotSpot 相同的功能。
有关 IBM JVM 的更详细信息,
Sun Java HotSpot VMJava HotSpot VM 是 Sun Microsystems 的 JVM 实现。虽然 Java HotSpot VM 与 Java II Platform VM 实现了相同的规范,并且运行相同的字节代码,但是它已经重新进行了设计,以利用一些新技术,例如自适应式优化,从而提高 JVM 的性能。Java Hotspot 中并没有包含 JIT 编译器插件,而是对程序中出现最多的方法(称为“热点”)进行优化、内联并执行其他优化选项。这意味着在 Java 字节码中首先传递的内容被解释为不存在本地代码编译器(例如 JIT)的字节码。如果代码是作为程序中的热点出现的,那么 HotSpot 编译器就会将字节码编译为本地代码,然后将其保存在高速缓存中,并同时进行其他方面的优化。与 JIT(只能自行使用,没有采用自适应式优化机制) 相比,选择编译方法的优点是字节码编译成本地代码之后,在那些可以从优化机制中获益的领域中,生成高度优化的本地代码可以节省很多时间,同时可以不用理会那些在解释模式中运行的代码。
IBM JVM JIT 和 MMI 用来提高 Java 程序性能的工具是 Just-In-Time(JIT)编译器。JIT 是一个代码生成器,它将 Java 字节码转换成宿主平台的本地代码。Java 程序使用 JIT 调用时的运行速度通常都比使用解释程序执行字节码时的速度更快。
当 JVM 启动时,会有很多方法被加载到 JVM 中并执行。
如果 JIT 被禁用了,那么 JVM 启动会很快,但是在大部分情况中,运行程序的速度都会与解释字节码的速度一样慢。如果 JIT 被启用了,但是并没有使用 JIT 的一些自适应式优化机制,就会试图在启动时对所有方法进行编译。对于诸如 applet 之类的小程序来说,当启动 JVM 的时间长于运行程序的时间时,就可能会成为问题。
IBM JVM Mixed Mode Interpreter(MMI)与 JIT 紧密地结合在一起,它可以减轻这个问题。它可以用于与 HotSpot VM 相同的目的,将编译方法延伸到 JVM 的生命期之外。除了其他特性之外,MMI 还可以计算一个特定的方法被执行了多少次。为了达到平衡的性能,MMI 对于 IBM JVM 支持的每种平台都有一个默认的上限计数器,这是经过仔细选择的,并经过了广泛的测试和研究。
每执行一次方法,该方法在 MMI 中的上限计数器就减 1。当一个方法的上限计数器达到零(0)时,就可以使用 JIT 将这个方法编译成本地代码了。因此,与 HotSpot VM 一样,高频率使用的方法 ——“热点”—— 都会在启动 JVM 之后由 JIT 进行编译,而低频度使用的方法则会在之后进行编译,或者可能在整个 JVM 进程的生命周期中都不会被编译。
IBM JVM 1.3.1 和 1.4.2 还为改进某些 Java 程序的启动时间而提供了一个非标准的选项 -Xquickstart。-Xquickstart 选项会导致使用优化选项的子集来运行 JIT;也就是说,快速编译的方法。这个选项适用于短期运行的程序,特别是那些执行时间不集中在少量的“热点”方法中的程序。如果 -Xquickstart 选项用在长时间运行的程序上,而这些程序又包含一些热点方法,那么这个选项就可能会导致性能的降低。
与其他非标准的 -X 选项一样,-Xquickstart 选项的实现和存在都可能不加通知就会修改。
可以减小 MMI 上限计数器,从而加速 JIT 编译一个方法的过程;或者增大它以达到相反的效果。这是通过使用一个环境变量 IBM_MIXED_MODE_THRESHOLD 实现的,与其他环境变量一样,需要在运行 JVM 进程的 shell 实例中进行设置。自然,这个变量是在启动 JVM 进程之前设置的,对于在 JVM 中执行的所有方法都是有效的。
将 IBM_MIXED_MODE_THRESHOLD 设置为 0 可以禁用 MMI,这样,所有的方法在首次加载到 JVM 中时就可以立即使用 JIT 进行编译。
IBM JVM 运行时模式如下:
- MMI 和 JIT 都启用 这是默认的 IBM JVM 设置。
- MMI 禁用,JIT 启用 所有的方法都是在首次运行之前编译的,即 JVM 的启动时间可能会很慢,但是后来的性能会很好。
- MMI 和 JIT 都禁用 JVM 是一个纯解释系统。所有的代码都不会进行编译。禁用 JIT 同时还会自动禁用 MMI,并将 IBM_MIXED_MODE_THRESHOLD 设置为 0,这样可以有效地禁用 MMI,但是不会禁用 JIT。
MMI 是一个非常有效的解释器,利用宿主平台上的程序集代码来达到最优的优化。虽然 JIT 并不是 JVM 的一个集成部分,但却以一个共享库(libjitc)的形式提供了,它与 MMI 紧密地结合在一起,JIT 和 MMI 是 IBM JVM 中紧密结合的两种技术。
在 Java HotSpot VM 或经典的 JIT 模式中使用的是 Sun JVM 1.3.1,Sun 的 JIT 实现并不与 IBM 的 JIT 完全相同;Sun HotSpot JVM 1.4.1 中根本没有 JIT。当将 Java 程序从 Sun JVM 1.3.1 或 Sun JVM 1.4.1 迁移到 IBM JVM 上时,在一些非常罕见的情况中可能会出现问题,问题的范围从性能的下降,到代码会产生不正确的结果,以及 JVM 的挂起、崩溃和出现一些异常。
JIT 诊断本节将介绍在问题发生时用来调试和诊断 IBM JVM 的 JIT 和 MMI 的技术。上一节简要介绍了 JIT,它是 IBM JVM 的一个基本部分。
在任何给定的时刻,JVM 进程包含一些可执行文件和一些使用 JIT 编译的代码,它们被动态链接到 JVM 中的 MMI 方法的封装程序上。JIT 编译的本地机器代码被放置到 JVM 本地数据内存段中,这样就可以增加 JVM 进程的本地内存占用,并且 MMI 封装程序被修改为指向编译后的代码。
因此,JIT 对于 Java 程序的执行流程会产生很大的影响。
在将程序从一个平台上迁移到另外一个平台上碰到的问题如下:
- 死锁挂起
- 一直产生不正确的结果
- 结果不一致
- 不正常结束
- 无限循环
- 内存泄漏
对问题原因要考虑的第一件事情是 JIT。
尽快在判断问题原因时,确定 JIT 是否是问题的根源非常重要,这是由于 3 个原因:
- 问题可能是由于 JIT 在 JVM 中给定的活动角色而引起的。
- JIT 调试与其他类型的问题判断技术有很大的不同。
- JIT 调试过程可能会非常耗费时间,而且非常复杂,通常需要高级的专门技术。
确定是否是 JIT 问题在某些情况下,从问题的特性可以很清楚地看出就是 JIT 的问题。例如,在 JVM 终止时带有 Javadump(在 Linux 上,Javadump 的文件名的格式为 javacore.YYYYMMDD.HHMMSS.PID.txt )或 Linux core 文件的情况下,从 Javadump 中的跟踪信息或 gdb 对 Java 可执行文件和 core 文件的输出信息中,可以很清楚地判断出 JIT 就是产生问题的原因。在某些情况下,会直接显示导致 JVM 进程死亡的信号是在 libjitc.so 中接收到的。在另外一些情况下,在 JVM 进程的代码中,但是在该进程的已编译代码之外,会产生崩溃或挂起,这可以说明问题是由于 JIT 编译的代码产生的。
然而,在大部分情况下,并没有清晰的迹象表明 JIT 是否是问题的源头。因此,给定 JIT 的重要性后,在问题判断过程中的第一个步骤应该是禁用 JIT,除非这显然不是一个与 JIT 相关的问题。即使在有迹象表明 JIT 就是问题的原因的情况下,最好也通过禁用 JIT 进行一下验证,并重新运行一下禁用了 JIT 的程序。
要禁用 JIT,首先请检查一下当前环境变量 JAVA_COMPILER 的设置,然后将其设置为 NONE。例如,对于 Bourne Again Shell (bash)或 Korn shell(ksh),设置如下:
1
| export JAVA_COMPILER=NONE
|
对于 csh,设置如下:
1
| setenv JAVA_COMPILER NONE
|
另外一种禁用 JIT 的方法是向 java 命令传递 -D 参数,将 java.compiler 设置为 NONE,从而覆盖默认的环境变量置:
1
| java -Djava.compiler=NONE <myapp>
|
JIT 默认是被启用的。要验证 JIT 是否被启用了,可以使用 java 命令的 -version 选项:
如果没有启用 JIT,就会显示一个包含如下内容的消息:
如果启用了 JIT,就会显示一个包含如下内容的消息:
如果指定了 JAVA_COMPILER="" 或 -Djava.compiler="",那就禁用了 JIT。如果 JAVA_COMPILER 没有设置,如下:
那么 JIT 编译器就启用了。
再次运行一下程序,看一下禁用 JIT 之后问题是否重现。如果问题可以重现,那么这就不是一个与 JIT 有关的问题。如果在禁用 JIT 之后问题就不存在了,那么这就可能是一个与 JIT 有关的问题。在禁用 JIT 之后问题就不再出现的现象并不意味着 JIT 编译器就是问题的原因。例如,高度线程化且时间相关的程序中的方法在编译后和解释时的运行速度可能会不同,因此 Java 代码中的逻辑错误只会在编译代码之后才会出现。
要启用 JIT,请将 JAVA_COMPILER 设置为 jitc,或者使用下面的命令行来切换 JIT 编译器:
1
| java -Djava.compiler=jitc <myapp>
|
或者取消 JAVA_COMPILER 环境变量的设置,或者从传递给 java 命令的选项中删除 -Djava.compiler 选项。 |
|
|
|
|
|