ZGCのセッションで、今後の長期目標の1つとして、JVMCIに対応する、と聞きました。逆に考えると、GCの中には、JVMCIに対応していないものがある、と理解しました。
現時点で新しめのOpenJDK(53918:616a32d6b463)で、試します。
デフォルト(G1GC)
$ build/macosx-x86_64-server-fastdebug/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -version openjdk version "13-internal" 2019-09-17 OpenJDK Runtime Environment (fastdebug build 13-internal+0-adhoc.koichisakata.jdk-jdk) OpenJDK 64-Bit Server VM (fastdebug build 13-internal+0-adhoc.koichisakata.jdk-jdk, mixed mode)
GCを指定しなければ、デフォルトのG1GCを使いますが、大丈夫です。
GCを指定
-XX:+UseParallelGC
、-XX:+UseParallelOldGC
、-XX:+UseSerialGC
と順に試しましたが、大丈夫です。
Epsilon
$ build/macosx-x86_64-server-fastdebug/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -XX:+UseEpsilonGC -version Error occurred during initialization of VM JVMCI Compiler does not support selected GC: epsilon gc
JVMCI Compiler does not support selected GC
と出ました。Epsilonは、JVMCIに対応していませんでした。
Shenandoah
JVMCI Compiler does not support selected GC: shenandoah gc
と出ました。Shenandoahも、JVMCIに対応していません。
ZGC
Linuxマシンで検証するのが、手間でしたので、やっていません。セッションで聞いたとおり、JVMCIに対応していないでしょう。
ガベージコレクタとJITコンパイラ
JVMCIは、JITコンパイラのインタフェースです。ガベージコレクタとJITコンパイラは、密接に関連するものですから、何かしら実装しければ、特定のガベージコレクタとJVMCIは組み合わせて使えないのだろう、と考えました。
JVMCIを使えるかどうかは、どこで判定しているのか?
簡単に見つかりました。
src/hotspot/share/jvmci/jvmci_globals.cpp void JVMCIGlobals::check_jvmci_supported_gc() { if (EnableJVMCI) { // Check if selected GC is supported by JVMCI and Java compiler if (!(UseSerialGC || UseParallelGC || UseParallelOldGC || UseG1GC)) { vm_exit_during_initialization("JVMCI Compiler does not support selected GC", GCConfig::hs_err_name()); FLAG_SET_DEFAULT(EnableJVMCI, false); FLAG_SET_DEFAULT(UseJVMCICompiler, false); } } }
検証どおり、4つのGC以外であればexitするとありました。
JVMCIに対応するには、何を実装しなければならないのか?
逆に、この4つのGCの実装を見れば、JVMCIの対応に必要な実装がわかるのでは、と考えました。検索で、まず目を引いたのは、次のようなコードでした。
SerialGC
src/hotspot/share/gc/serial/genMarkSweep.cpp // Don't add any more derived pointers during phase3 #if COMPILER2_OR_JVMCI assert(DerivedPointerTable::is_active(), "Sanity"); DerivedPointerTable::set_active(false); #endif
ParallelGC
src/hotspot/share/gc/parallel/psScavenge.cpp ... #if COMPILER2_OR_JVMCI DerivedPointerTable::clear(); #endif ... #if COMPILER2_OR_JVMCI DerivedPointerTable::update_pointers(); #endif
G1GC
src/hotspot/share/gc/g1/g1FullCollector.cpp ... static void clear_and_activate_derived_pointers() { #if COMPILER2_OR_JVMCI DerivedPointerTable::clear(); #endif } static void deactivate_derived_pointers() { #if COMPILER2_OR_JVMCI DerivedPointerTable::set_active(false); #endif } static void update_derived_pointers() { #if COMPILER2_OR_JVMCI DerivedPointerTable::update_pointers(); #endif }
他多数。
DerivedPointerTable
DerivedPointerTableは、当然DerivedPointer(派生ポインタ。オブジェクトの参照にオフセットを加算して得られたポインタ)のテーブルです。まずは、これを見ます。
src/hotspot/share/compiler/oopMap.hpp // Derived pointer support. This table keeps track of all derived points on a // stack. It is cleared before each scavenge/GC. During the traversal of all // oops, it is filled in with references to all locations that contains a // derived oop (assumed to be very few). When the GC is complete, the derived // pointers are updated based on their base pointers new value and an offset. #if COMPILER2_OR_JVMCI class DerivedPointerTable : public AllStatic { friend class VMStructs; private: static GrowableArray<DerivedPointerEntry*>* _list; static bool _active; // do not record pointers for verify pass etc. public: static void clear(); // Called before scavenge/GC static void add(oop *derived, oop *base); // Called during scavenge/GC static void update_pointers(); // Called after scavenge/GC static bool is_empty() { return _list == NULL || _list->is_empty(); } static bool is_active() { return _active; } static void set_active(bool value) { _active = value; } };
「派生ポインタのサポート。このテーブルはスタック上の全派生ポインタを記録する。各scavenge/GCの前にクリアする。全oopのトラバースの間、派生oopがある全ロケーションへの参照がここに書き込まれる(ほとんどない想定)。GC完了時、派生ポインタは、それらのベースとなっているポインタの新しい値とオフセットに基づいて、更新される。」とあります。
このテーブルはscavenge GCの前にクリアし、GC完了時に管理している派生ポインタを更新する必要がある、ということですね。
と、ここまで来て、ふと思いました。Epsilonは、GCしないので、このあたりのことを考慮しなくてよいはず、すでにJVMCIと合わせられそうな気もする、と…
Epsilon + JVMCI
jvmci_globals.cppに、Epsilonを書き加えて、ビルドします。
void JVMCIGlobals::check_jvmci_supported_gc() { if (EnableJVMCI) { // Check if selected GC is supported by JVMCI and Java compiler if (!(UseSerialGC || UseParallelGC || UseParallelOldGC || UseG1GC || UseEpsilonGC)) { ... } } }
試します。
$ build/macosx-x86_64-server-fastdebug/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -XX:+UseEpsilonGC -version openjdk version "13-internal" 2019-09-17 OpenJDK Runtime Environment (fastdebug build 13-internal+0-adhoc.koichisakata.jdk-jdk) OpenJDK 64-Bit Server VM (fastdebug build 13-internal+0-adhoc.koichisakata.jdk-jdk, mixed mode)
exitしなくなりました。実際にアプリケーションを動かしてみます。-XX:+PrintCompilation
をつけて、JITコンパイルのログを出力させます。
$ build/macosx-x86_64-server-fastdebug/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+UseJVMCICompiler -XX:+PrintCompilation -jar target/*.jar ... org.graalvm.compiler.debug.GraalError: Epsilon garbage collector is not supported by Graal at jdk.internal.vm.compiler/org.graalvm.compiler.hotspot.HotSpotGraalRuntime.getSelectedGC(HotSpotGraalRuntime.java:252) at jdk.internal.vm.compiler/org.graalvm.compiler.hotspot.HotSpotGraalRuntime.<init>(HotSpotGraalRuntime.java:153) at jdk.internal.vm.compiler/org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory.createCompiler(HotSpotGraalCompilerFactory.java:145) at jdk.internal.vm.compiler/org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory.createCompiler(HotSpotGraalCompilerFactory.java:123) at jdk.internal.vm.compiler/org.graalvm.compiler.hotspot.HotSpotGraalCompilerFactory.createCompiler(HotSpotGraalCompilerFactory.java:47) at jdk.internal.vm.ci/jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.getCompiler(HotSpotJVMCIRuntime.java:425) at jdk.internal.vm.ci/jdk.vm.ci.hotspot.HotSpotJVMCIRuntime.compileMethod(HotSpotJVMCIRuntime.java:524)
Graalでエラーが出ました。JVMCIは、特定のJITコンパイラを指定しければ、Graal JITコンパイラを使用するからです。Javaコードを見ます。
private HotSpotGC getSelectedGC() throws GraalError { for (HotSpotGC gc : HotSpotGC.values()) { if (gc.isSelected(config)) { if (!gc.supported) { throw new GraalError(gc.name() + " garbage collector is not supported by Graal"); } return gc; } } // As of JDK 9, exactly one GC flag is guaranteed to be selected. // On JDK 8, the default GC is Serial when no GC flag is true. return HotSpotGC.Serial; }
Enum HotSpotGCでsupportedがfalseだと、GraalErrorとなるようです。
public enum HotSpotGC { // Supported GCs Serial(true, "UseSerialGC"), Parallel(true, "UseParallelGC", "UseParallelOldGC", "UseParNewGC"), CMS(true, "UseConcMarkSweepGC"), G1(true, "UseG1GC"), // Unsupported GCs Epsilon(false, "UseEpsilonGC"), Z(false, "UseZGC"); HotSpotGC(boolean supported, String... flags) { this.supported = supported; this.flags = flags; } }
EpsilonとZGCは、supportedがfalseでした。試しに、Epsilonをtrueにしてビルドし、アプリケーションを実行します。
$ build/macosx-x86_64-server-fastdebug/jdk/bin/java -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -XX:+PrintCompilation -XX:+UseEpsilonGC -Xlog:gc -Xmx2G -jar target/*.jar [0.030s][info][gc] Resizeable heap; starting at 256M, max: 2048M, step: 128M [0.030s][info][gc] Using TLAB allocation; max: 4096K [0.030s][info][gc] Elastic TLABs enabled; elasticity: 1.10x [0.030s][info][gc] Elastic TLABs decay enabled; decay time: 1000ms [0.030s][info][gc] Using Epsilon ... 14719 4298 4 java.lang.StringLatin1::inflate (34 bytes)
何度か試しましたが、起動が途中で止まります。JITコンパイルのログ上では、違う部分で止まります。エラーも何も出力されないので、ここからは先に進めそうにありません。
また時間を取って、探っていきます。