Graal JIT 編譯器操作手冊

效能測量 #

測量效能時,首先要確認 Java 虛擬機器 (JVM) 是否正在使用 Graal JIT 編譯器。

GraalVM 預設設定為將 Graal JIT 編譯器作為頂層編譯器使用。

若要在 Java HotSpot 虛擬機器 中啟用 Graal JIT 編譯器,請使用 -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 上開啟一個 issue。附上 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 上的 issue。

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

  • Print:將訊息和堆疊追蹤列印到主控台,但不執行重新編譯。
  • ExitVM:與 Diagnose 相同,但 JVM 程序會在重新編譯後退出。

程式碼產生錯誤 #

您在使用編譯器時可能會遇到的另一種錯誤是產生不正確的機器碼。此錯誤可能會導致 JVM 當機,進而導致在 JVM 程序的目前工作目錄中產生一個以 hs_err_pid 開頭的檔案。在大多數情況下,檔案中會有一個區段顯示當機時的堆疊,包括堆疊中每個框架的程式碼類型,如下例所示

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)。當機發生在為下列項目產生的機器碼中的位移 0x141

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

堆疊中的下兩個框架是解譯的 (j)。當機位置也經常在檔案頂端附近指出,如下所示

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

在此範例中,Graal JIT 編譯器為 NodeLIRBuilder.matchComplexExpressions 產生的程式碼中可能存在錯誤。

當在 GitHub 上提交這類當機的 issue 時,您應先嘗試在啟用有問題方法編譯的額外診斷的情況下重現當機。在此範例中,您會在命令列中新增以下選項

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

這些選項在編譯器偵錯文件中有更詳細的說明。簡而言之,這些選項會告知 Graal JIT 編譯器在編譯任何名為 matchComplexExpressions 的方法時,以詳細等級 2 擷取其狀態快照,該方法位於具有簡單名稱 NodeLIRBuilder 的類別中。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 上的 issue。

與我們聯繫