JavaのGC方式の決定条件について

# はじめに

JavaのGC方式(ParallelGC、G1GCなど)がオプションにより指定されていない場合、どのように決定されているかJavaVMソースコードで処理を追ってみた。
ソースは OpenJDK Java11 のもの。以下のソースパスで src/hotspot は省略している。

# 結論

先に結論を書くと、GC方式がオプションにより指定されていない場合、以下のようになる。

|条件 |GC方式 |
|:-:|:-:|
|サーバクラスマシン|UseG1GC|
|クライアントクラスマシン|UseSerialGC|

サーバクラスマシンは基本的にCPU2個以上かつメモリ約2G以上のマシン。

# ソース詳細

JavaVM起動時に以下のような順で呼び出される。

    Threads::create_vm (share/runtime/thread.cpp)
      Arguments::apply_ergo (share/runtime/arguments.cpp)
        Arguments::set_ergonomics_flags
          GCConfig::initialize (share/gc/shared/gcConfig.cpp)
            select_gc
              select_gc_ergonomically

各ソースを順に追っていく。

```cpp:share/runtime/thread.cpp
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
  ....
  // Parse arguments
  // Note: this internally calls os::init_container_support()
  jint parse_result = Arguments::parse(args);
  if (parse_result != JNI_OK) return parse_result;

  os::init_before_ergo();

  jint ergo_result = Arguments::apply_ergo();   // ★
  if (ergo_result != JNI_OK) return ergo_result;
  ...
```

Arguments::parse でjavaの引数がパースされ、GC方式が指定されていると、そのオプションがtrueになる。
例えば -XX:+UseParallelGC なら、UseParallelGC がtrueになる。
基本的に、JVMの各オプションの値は、ソース上ではオプションの名前の変数が使われている。


```cpp:share/runtime/arguments.cpp
jint Arguments::apply_ergo() {
  // Set flags based on ergonomics.
  jint result = set_ergonomics_flags();  //  ★
  if (result != JNI_OK) return result;

  .....

jint Arguments::set_ergonomics_flags() {
  GCConfig::initialize();   //  ★

  set_conservative_max_heap_alignment();
  .....
```

```cpp:share/gc/shared/gcConfig.cpp
void GCConfig::initialize() {
  assert(_arguments == NULL, "Already initialized");
  _arguments = select_gc();  //  ★
}
....
GCArguments* GCConfig::select_gc() {
  // Fail immediately if an unsupported GC is selected
  fail_if_unsupported_gc_is_selected();

  if (is_no_gc_selected()) {   //  ★ GC方式が指定されていなかったら
    // Try select GC ergonomically
    select_gc_ergonomically();    //  ★ ここでGC方式を決定
    .....

```

## is_no_gc_selected

is_no_gc_selected はどの方式も指定されてないとtrue、GC方式がすでに指定されていると、falseを返す。

```cpp:share/gc/shared/gcConfig.cpp
bool GCConfig::is_no_gc_selected() {
  FOR_EACH_SUPPORTED_GC(gc) {   //  ★
    if (gc->_flag) {
      return false;
    }
  }

  return true;
}
```

FOR_EACH_SUPPORTED_GC は以下で定義。

```cpp:
#define FOR_EACH_SUPPORTED_GC(var)                                          \
  for (const SupportedGC* var = &SupportedGCs[0]; var < &SupportedGCs[ARRAY_SIZE(SupportedGCs)]; var++)
```

SupportedGCs にはサポートされているGC方式が羅列されている。

```cpp:
static const SupportedGC SupportedGCs[] = {
       CMSGC_ONLY_ARG(SupportedGC(UseConcMarkSweepGC, CollectedHeap::CMS,      cmsArguments,      "concurrent mark sweep gc"))
   EPSILONGC_ONLY_ARG(SupportedGC(UseEpsilonGC,       CollectedHeap::Epsilon,  epsilonArguments,  "epsilon gc"))
        G1GC_ONLY_ARG(SupportedGC(UseG1GC,            CollectedHeap::G1,       g1Arguments,       "g1 gc"))
  PARALLELGC_ONLY_ARG(SupportedGC(UseParallelGC,      CollectedHeap::Parallel, parallelArguments, "parallel gc"))
  PARALLELGC_ONLY_ARG(SupportedGC(UseParallelOldGC,   CollectedHeap::Parallel, parallelArguments, "parallel gc"))
    SERIALGC_ONLY_ARG(SupportedGC(UseSerialGC,        CollectedHeap::Serial,   serialArguments,   "serial gc"))
         ZGC_ONLY_ARG(SupportedGC(UseZGC,             CollectedHeap::Z,        zArguments,        "z gc"))
};
```

## select_gc_ergonomically

ここでサーバクラスマシンか否かによってGC方式を決定する。

```cpp:
void GCConfig::select_gc_ergonomically() {
  if (os::is_server_class_machine()) {
#if INCLUDE_G1GC
    FLAG_SET_ERGO_IF_DEFAULT(bool, UseG1GC, true);  // ★ サーバクラスマシンならG1GC
#elif INCLUDE_PARALLELGC
    FLAG_SET_ERGO_IF_DEFAULT(bool, UseParallelGC, true);
#elif INCLUDE_SERIALGC
    FLAG_SET_ERGO_IF_DEFAULT(bool, UseSerialGC, true);
#endif
  } else {
#if INCLUDE_SERIALGC
    FLAG_SET_ERGO_IF_DEFAULT(bool, UseSerialGC, true);  // ★ クライアントクラスマシンならSerialGC
#endif
  }
}
```

## is_server_class_machine

is_server_class_machine では、以下の条件でtrue(つまりサーバクラスマシン) または、false を返す。

|条件 |返却値 |
|:-:|:-:|
| オプション NeverActAsServerClassMachine 指定時|false |
| オプション AlwaysActAsServerClassMachine 指定時|true |
|CPU2個以上かつメモリ約2G以上|true|
|それ以外|false|

```cpp:share/runtime/os.cpp
bool os::is_server_class_machine() {
  // First check for the early returns
  if (NeverActAsServerClassMachine) {  //★ NeverActAsServerClassMachine 指定の場合、サーバクラスマシンでないとみなす
    return false;
  }
  if (AlwaysActAsServerClassMachine) { //★ AlwaysActAsServerClassMachine 指定の場合、サーバクラスマシンとみなす。
    return true;
  }
  // Then actually look at the machine
  bool         result            = false;
  const unsigned int    server_processors = 2;
  const julong server_memory     = 2UL * G;
  // We seem not to get our full complement of memory.
  //     We allow some part (1/8?) of the memory to be "missing",
  //     based on the sizes of DIMMs, and maybe graphics cards.
  const julong missing_memory   = 256UL * M;

  /* Is this a server class machine? */  //★CPU2個以上かつメモリ約2G以上ならtrue
  if ((os::active_processor_count() >= (int)server_processors) &&
      (os::physical_memory() >= (server_memory - missing_memory))) {
    const unsigned int logical_processors =
      VM_Version::logical_processors_per_package();
    if (logical_processors > 1) {
      const unsigned int physical_packages =
        os::active_processor_count() / logical_processors;
      if (physical_packages >= server_processors) {
        result = true;
      }
    } else {
      result = true;
    }
  }
  return result;
}
```