Graal JIT 编译器操作手册

性能测量 #

测量性能时,首先要确认 Java 虚拟机 (JVM) 是否正在使用 Graal JIT 编译器。

GraalVM 默认配置为使用 Graal JIT 编译器作为最高层编译器。

要启用 Graal JIT 编译器以在 Java HotSpot 虚拟机 中使用,请使用 -XX:+UseGraalJIT 选项。(-XX:+UseGraalJIT 选项必须与 -XX:+UnlockExperimentalVMOptions 选项一起使用,该选项解锁此实验性集成。)

以下示例使用启用的 Graal JIT 编译器运行 Java 应用程序 com.example.myapp

java -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT com.example.myapp

您可以通过将 -Djdk.graal.ShowConfiguration=info 选项添加到命令行来确认您正在使用 Graal JIT 编译器。当编译器初始化时,它会生成类似于以下内容的一行输出

Using "Graal Enterprise compiler with Truffle extensions" loaded from a PGO optimized Native Image shared library

注意:Graal 编译器仅在第一个最高层 JIT 编译请求时初始化,因此如果您的应用程序寿命很短,您可能看不到此输出。

优化基于 JVM 的应用程序本身就是一门科学。在性能不佳的情况下,编译甚至可能不是一个因素,因为问题可能存在于 JVM 的任何其他部分(I/O、垃圾收集、线程等),或在编写不当的应用程序或第三方库代码中。因此,值得使用 JDK Mission Control 工具链来诊断应用程序的行为。

您也可以通过将 -XX:-UseJVMCICompiler 添加到命令行来将性能与 JVM 中的本机最高层编译器进行比较。

如果您在使用 Graal JIT 编译器时观察到明显的性能下降,请在 GitHub 上打开一个问题。附上 Java Flight Recorder 日志和重现问题的说明,这将使调查更容易,从而增加修复的可能性。如果您可以提交一个 JMH 基准测试,该基准测试代表您应用程序中最热的部分(由分析器识别),那就更好了。这使我们能够快速查明缺少的优化机会,或提供有关如何重构代码以避免或减少性能瓶颈的建议。

对 Graal JIT 编译器进行故障排除 #

如果您发现安全漏洞,请不要通过 GitHub Issues 或公共邮件列表报告,而是通过 报告漏洞指南 中概述的流程进行报告。

编译异常 #

编译器是用 Java 编写的,这一个优点是,编译期间的 Java 异常不是致命的 JVM 错误。相反,每个编译都有一个异常处理程序,该处理程序根据 graal.CompilationFailureAction 属性采取行动。

默认值为 Silent。如果您指定 Diagnose,则会重试失败的编译,并提供额外的诊断信息。在这种情况下,在 JVM 退出之前,将在重新编译期间捕获的所有诊断输出都写入 ZIP 文件,并且其位置将在控制台上打印出来,例如

Graal diagnostic output saved in /Users/demo/graal-dumps/1499768882600/graal_diagnostics_64565.zip

然后,您可以将 ZIP 文件附加到 GitHub 上的问题。

除了 SilentDiagnose 之外,还提供以下 graal.CompilationFailureAction

  • Print:在控制台上打印消息和堆栈跟踪,但不执行重新编译。
  • ExitVM:与 Diagnose 相同,但在重新编译后 JVM 进程退出。

代码生成错误 #

您在编译器中可能遇到的另一种错误是生成不正确的机器代码。此错误会导致 JVM 崩溃,从而生成一个以 hs_err_pid 开头的文件,位于 JVM 进程的当前工作目录中。在大多数情况下,文件中有一个部分显示崩溃时的堆栈,包括堆栈中每个帧的代码类型,如下例所示

Stack: [0x00007000020b1000,0x00007000021b1000],  sp=0x00007000021af7a0,  free space=1017k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
J 761 JVMCI jdk.graal.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V (299 bytes) @ 0x0000000108a2fc01 [0x0000000108a2fac0+0x141] (null)
j  jdk.graal.compiler.core.gen.NodeLIRBuilder.doBlock(Ljdk.graal.compiler/nodes/cfg/Block;Ljdk.graal.compiler/nodes/StructuredGraph;Ljdk.graal.compiler/core/common/cfg/BlockMap;)V+211
j  jdk.graal.compiler.core.LIRGenerationPhase.emitBlock(Ljdk.graal.compiler/nodes/spi/NodeLIRBuilderTool;Ljdk.graal.compiler/lir/gen/LIRGenerationResult;Ljdk.graal.compiler/nodes/cfg/Block;Ljdk.graal.compiler/nodes/StructuredGraph;Ljdk.graal.compiler/core/common/cfg/BlockMap;)V+65

此示例表明,顶层帧由 JVMCI 编译器(即 Graal JIT 编译器)编译 (J)。崩溃发生在为

jdk.graal.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V

生成的机器代码的偏移量 0x141 处。堆栈中的接下来的两个帧被解释 (j)。崩溃的位置也经常在文件顶部附近用类似以下内容表示

# Problematic frame:
# J 761 JVMCI jdk.graal.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V (299 bytes) @ 0x0000000108a2fc01 [0x0000000108a2fac0+0x141] (null)

在此示例中,NodeLIRBuilder.matchComplexExpressions 的 Graal JIT 编译器生成的代码中可能存在错误。

当为 GitHub 上的此类崩溃提交问题时,您应首先尝试使用为问题方法的编译启用的额外诊断信息来重现崩溃。在此示例中,您将以下选项添加到您的命令行

-Djdk.graal.MethodFilter=NodeLIRBuilder.matchComplexExpressions, -Djdk.graal.Dump=:2

这些选项在 编译器调试文档 中有更详细的描述。简而言之,这些选项告诉 Graal JIT 编译器在编译任何名为 matchComplexExpressions 且简单名称为 NodeLIRBuilder 的类中的方法时,以详细程度级别 2 捕获其状态的快照。MethodFilter 选项的完整格式在 MethodFilterHelp.txt 中描述。

很多时候,崩溃位置并不直接存在于崩溃日志中提到的问题方法中,而是来自内联方法。

在这种情况下,仅筛选问题方法可能无法捕获导致崩溃的错误编译。

为了提高捕获错误编译的可能性,请扩大 MethodFilter 值。为了指导这一点,请在尝试重现崩溃时添加 -Djdk.graal.PrintCompilation=true 选项,以便您可以看到崩溃之前编译了什么。

以下显示了控制台的示例输出

HotSpotCompilation-1218        Ljdk.graal.compiler/core/amd64/AMD64NodeLIRBuilder;                  peephole                                      (Ljdk.graal.compiler/nodes/ValueNode;)Z           |   87ms   428B   447B  1834kB
HotSpotCompilation-1212        Ljdk.graal.compiler/lir/LIRInstructionClass;                         forEachState                                  (Ljdk.graal.compiler/lir/LIRInstruction;Ljdk.graal.compiler/lir/InstructionValueProcedure;)V  |  359ms    92B   309B  6609kB
HotSpotCompilation-1221        Ljdk.graal.compiler/hotspot/amd64/AMD64HotSpotLIRGenerator;          getResult                                     ()Ljdk.graal.compiler/hotspot/HotSpotLIRGenerationResult;  |   54ms    18B   142B  1025kB
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x000000010a6cafb1, pid=89745, tid=0x0000000000004b03
#
# JRE version: OpenJDK Runtime Environment (8.0_121-b13) (build 1.8.0_121-graalvm-olabs-b13)
# Java VM: OpenJDK 64-Bit GraalVM (25.71-b01-internal-jvmci-0.30 mixed mode bsd-amd64 compressed oops)
# Problematic frame:
# J 1221 JVMCI jdk.graal.compiler.hotspot.amd64.AMD64HotSpotLIRGenerator.getResult()Ljdk.graal.compiler/hotspot/HotSpotLIRGenerationResult; (18 bytes) @ 0x000000010a6cafb1 [0x000000010a6caf60+0x51] (null)
#
# Failed to write core dump. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again

在这里,崩溃发生在与第一个崩溃不同的方法中。因此,我们将过滤器参数扩展为 -Djdk.graal.MethodFilter=NodeLIRBuilder.matchComplexExpressions,AMD64HotSpotLIRGenerator.getResult 并再次运行。

当 JVM 以这种方式崩溃时,它不会运行存档 Graal 编译器诊断输出或删除写入它的目录的关闭代码。这必须在崩溃后手动完成。

默认情况下,目录为 $PWD/graal-dumps/<timestamp>(例如,./graal-dumps/1499938817387)。但是,您可以使用 -Djdk.graal.DumpPath=<path> 选项指定目录。

当编译器第一次使用此目录时,将在控制台上打印类似以下内容的消息

Dumping debug output in /Users/demo/graal-dumps/1499768882600

此目录应包含与崩溃方法相关的内容,例如

ls -l /Users/demo/graal-dumps/1499768882600
-rw-r--r--  1 demo  staff    144384 Jul 13 11:46 HotSpotCompilation-1162[AMD64HotSpotLIRGenerator.getResult()].bgv
-rw-r--r--  1 demo  staff     96925 Jul 13 11:46 HotSpotCompilation-1162[AMD64HotSpotLIRGenerator.getResult()].cfg
-rw-r--r--  1 demo  staff  12600725 Jul 13 11:46 HotSpotCompilation-791[NodeLIRBuilder.matchComplexExpressions(List)].bgv
-rw-r--r--  1 demo  staff   1727409 Jul 13 11:46 HotSpotCompilation-791[NodeLIRBuilder.matchComplexExpressions(List)].cfg

您应该将此目录的 ZIP 文件附加到 GitHub 上的问题。

联系我们