JVM操作マニュアル

GraalVMコンパイラのネイティブ・イメージでの実行とJVMでの実行

GraalVMコンパイラをJVMで実行すると、他のJavaアプリケーションと同じウォームアップ・フェーズを経由します。つまり、ホット・メソッドがコンパイルされる前に最初に解釈されます。このため、JVMでのネイティブ・コンパイラ(C1、C2など)と比較して、アプリケーションがピーク・パフォーマンスに到達するまでの時間がわずかに長くなる可能性があります。

ピーク・パフォーマンスに到達するまでの時間が長くなる問題に対処するために、libgraal (コンパイラ自体を事前にコンパイルするためにネイティブ・イメージを使用して生成された共有ライブラリ)が導入されました。つまり、GraalVM Enterpriseコンパイラはネイティブ共有ライブラリとしてデプロイされます。

このモードでは、コンパイラはHotSpotヒープとは別のメモリーを使用し、最初からコンパイルされた状態で実行されます。したがって、C1やC2などの他のネイティブHotSpotコンパイラと同様の実行プロパティがあります。現在、これが操作のデフォルトのモードです。これは、-XX:-UseJVMCINativeLibraryを使用して無効にすることができます。

パフォーマンスの測定

パフォーマンスを測定する際には、JVMがGraalVM Enterpriseコンパイラを使用していることを最初に確認する必要があります。GraalVMバイナリでは、JVMはデフォルトでGraalVMコンパイラを最上位層のコンパイラとして使用するように構成されています。これを確認するには、コマンドラインに-Dgraal.ShowConfiguration=infoを追加します。これにより、コンパイラが初期化されるとき、次のような出力行が生成されます:

Using Graal compiler configuration 'community' provided by org.graalvm.compiler.hotspot.CommunityCompilerConfigurationFactory loaded from jar:file:/Users/dsimon/graal/graal/compiler/mxbuild/dists/graal.jar!/org/graalvm/compiler/hotspot/CommunityCompilerConfigurationFactory.class

ノート: GraalVMコンパイラは、最上位層のJITコンパイルが初めてリクエストされたときにのみ初期化されるため、アプリケーションの存続期間が短い場合、この出力が表示されないことがあります。

JVMベースのアプリケーションの最適化は、それ自体が1つの知識体系です。VMの他の部分(I/O、ガベージ・コレクション、スレッドなど)か、アプリケーションやサード・パーティのライブラリ・コードの不適切な記述に問題がある場合があるため、コンパイルがパフォーマンスの低下の要因ではない可能性があります。このため、JDK Mission Controlツール・チェーンを使用してアプリケーションの動作を診断することをお薦めします。

コマンドラインに-XX:-UseJVMCICompilerを追加することで、JVMのネイティブの最上位層のコンパイラとパフォーマンスを比較することもできます。

GraalVMコンパイラの使用時にパフォーマンスが大幅に低下する場合は、GitHubで問題をオープンしてください。Javaフライト・レコーダのログや問題を再現する手順を添付すると、調査が容易になり、修正される可能性が高くなります。アプリケーションで最もホットである(ことがプロファイラによって識別された)部分を示すJMHベンチマークを提出できると、さらに役立ちます。これにより、欠けている最適化の機会の迅速な特定や、パフォーマンス・ボトルネックを回避または削減するようにコードを再構築する方法に関する提案が可能になります。

GraalVMコンパイラのトラブルシューティング

すべてのソフトウェアと同様に、GraalVMコンパイラはバグがないことが保証されていないため、問題が発生した場合に有用なバグ・レポートを診断して提出する方法を知っておくと役立ちます。

セキュリティの脆弱性を発見した場合は、GitHub Issuesや公開メーリング・リストではなく脆弱性の報告ガイドで概説されているプロセスに従って報告してください。

コンパイル例外

コンパイラをJavaで記述する利点の1つは、コンパイル中のランタイム例外が致命的なVMエラーではないことです。かわりに、各コンパイルには、graal.CompilationFailureActionプロパティに基づいてアクションを実行する例外ハンドラがあります。

デフォルト値はSilentです。Diagnoseを指定すると、失敗したコンパイルは追加の診断を有効にして再試行されます。この場合、VMが終了する直前に、再試行されたコンパイル中に取得されたすべての診断出力が.zipファイルに書き込まれ、その場所がコンソールに出力されます:

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

その後、.zipファイルをGitHubで問題に添付できます。

SilentおよびDiagnoseとは別に、graal.CompilationFailureActionでは次の値もサポートされています:

コード生成エラー

コンパイラで発生する可能性のあるその他のタイプのエラーは、不正なマシン・コードの生成です。このエラーによってVMクラッシュが発生し、VMプロセスの現在の作業ディレクトリに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 org.graalvm.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V (299 bytes) @ 0x0000000108a2fc01 [0x0000000108a2fac0+0x141] (null)
j  org.graalvm.compiler.core.gen.NodeLIRBuilder.doBlock(Lorg/graalvm/compiler/nodes/cfg/Block;Lorg/graalvm/compiler/nodes/StructuredGraph;Lorg/graalvm/compiler/core/common/cfg/BlockMap;)V+211
j  org.graalvm.compiler.core.LIRGenerationPhase.emitBlock(Lorg/graalvm/compiler/nodes/spi/NodeLIRBuilderTool;Lorg/graalvm/compiler/lir/gen/LIRGenerationResult;Lorg/graalvm/compiler/nodes/cfg/Block;Lorg/graalvm/compiler/nodes/StructuredGraph;Lorg/graalvm/compiler/core/common/cfg/BlockMap;)V+65

この例は、最上位のフレームがJVMCIコンパイラ(これはGraalVMコンパイラです)によってコンパイルされた(J)ことを示しています。次に対して生成されたマシン・コードのオフセット0x141でクラッシュが発生しました:

org.graalvm.compiler.core.gen.NodeLIRBuilder.matchComplexExpressions(Ljava/util/List;)V

スタック内の次の2つのフレームは、インタプリタで実行されました(j)。クラッシュの場所も、多くの場合、次のようなものでファイルの上部付近に示されます:

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

この例では、NodeLIRBuilder.matchComplexExpressionsに対してGraalVMコンパイラによって生成されたコードにエラーがある可能性があります。

このようなクラッシュについてGitHubで問題を提出する場合、まず、問題のあるメソッドのコンパイルに対して追加の診断を有効にして、クラッシュの再現を試みる必要があります。この例では、次をコマンドラインに追加します:

-Dgraal.MethodFilter=NodeLIRBuilder.matchComplexExpressions, -Dgraal.Dump=:2

これらのオプションの詳細は、ここを参照してください。簡単に言うと、これらのオプションは、NodeLIRBuilderという単純名を持つクラス内のmatchComplexExpressionsという名前のメソッドのコンパイル時に、コンパイラの状態のスナップショットを冗長レベル2で取得するようにコンパイラに指定します。MethodFilterオプションの完全な形式については、java -XX:+JVMCIPrintPropertiesの出力で説明されています。

多くの場合、クラッシュの場所はクラッシュ・ログで示された問題のあるメソッド自体には存在せず、インライン化されたメソッドから発生しています。

このような場合、単に問題のあるメソッドをフィルタ処理しても、クラッシュの原因となった誤ったコンパイルを捕捉できないことがあります。

誤ったコンパイルを捕捉する可能性を高めるには、MethodFilter値を拡大する必要があります。そのためには、クラッシュの直前に何がコンパイルされたかを確認できるように、クラッシュの再現を試みるときに-Dgraal.PrintCompilation=trueを追加します。

次に、コンソールからの出力例を示します:

HotSpotCompilation-1218        Lorg/graalvm/compiler/core/amd64/AMD64NodeLIRBuilder;                  peephole                                      (Lorg/graalvm/compiler/nodes/ValueNode;)Z           |   87ms   428B   447B  1834kB
HotSpotCompilation-1212        Lorg/graalvm/compiler/lir/LIRInstructionClass;                         forEachState                                  (Lorg/graalvm/compiler/lir/LIRInstruction;Lorg/graalvm/compiler/lir/InstructionValueProcedure;)V  |  359ms    92B   309B  6609kB
HotSpotCompilation-1221        Lorg/graalvm/compiler/hotspot/amd64/AMD64HotSpotLIRGenerator;          getResult                                     ()Lorg/graalvm/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 org.graalvm.compiler.hotspot.amd64.AMD64HotSpotLIRGenerator.getResult()Lorg/graalvm/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

ここでは、最初のクラッシュとは異なるメソッドでクラッシュが発生したことがわかります。そのため、フィルタ引数を-Dgraal.MethodFilter=NodeLIRBuilder.matchComplexExpressions,AMD64HotSpotLIRGenerator.getResultに拡大して、再度実行します。

この方法でVMがクラッシュした場合、GraalVMコンパイラの診断出力をアーカイブするシャットダウン・コードが実行されないか、それが書き込まれたディレクトリが削除されます。これは、クラッシュ後に手動で行う必要があります。

デフォルトでは、ディレクトリは$PWD/graal-dumps/<timestamp>です(たとえば、./graal-dumps/1499938817387)。ただし、-Dgraal.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

GitHubで、このディレクトリの.zipを問題に添付してください。