Fight the Future

Java言語とJVM、そしてJavaエコシステム全般にまつわること

ガベージコレクタとJVMCI

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コンパイルのログ上では、違う部分で止まります。エラーも何も出力されないので、ここからは先に進めそうにありません。

また時間を取って、探っていきます。