MaxHeapSize,InitialHeapSizeのデフォルト値

# はじめに

MaxHeapSize(-Xmx)、InitialHeapSizeのデフォルト値は公式ドキュメントに少し書かれていますが、詳しい記載がありません。
そこでソースと合わせて調べて見ました。

対象バージョンは OpenJDK 11
実機確認環境は CentOS7  

# 結論
## MaxHeapSize
MaxHeapSizeは以下のように決定される。

|メモリサイズ(※1)|値|
|:-:|:-:|
|約248M以下(※2)  |   メモリサイズ * 0.5  (※4)|
|約248M～約496M以下 | MaxHeapSize(124MB) |
|約496M～約120G以下(※3) | メモリサイズ * 0.25 (※5)|
|約120G以上     |          約30G(※6)|

さらに詳細。

(※1)
MaxRAM の指定があれば、その値、なければ実際のメモリサイズ。
以降、メモリサイズはこの意味で使用。

(※2)
248M は以下より。
MaxHeapSize / (MinRAMPercentage/100) = 124M / (50/100) = 248M

(※4)
0.5 はMinRAMPercentageの値であり、厳密には以下。
メモリサイズ * (MinRAMPercentage/100)

(※5)
0.25 はMaxRAMPercentageの値であり、厳密には以下。
メモリサイズ * (MaxRAMPercentage/100)

(※6)
30GB は以下より。
32GB - HeapBaseMinAddress (=2GB)
32GB はCompressedOops使用時のJavaヒープ最大オフセット。詳細はソースのところで説明。

## InitialHeapSize
InitialHeapSize のデフォルト値は以下のように決定される。
以下のどちらか大きい方(ただし、MaxHeapSizeを超えない)

- メモリサイズ * InitialRAMPercentage  
- OldSize + NewSize

# ソース

## MaxHeapSize の初期デフォルト値

MaxHeapSizeの最初のデフォルト値は以下で定義されている。
以下のソースパスで src/hotspot は省略している。

```cpp:share/gc/shared/gc_globals.hpp

  product(size_t, MaxHeapSize, ScaleForWordSize(96*M),                      \
          "Maximum heap size (in bytes)")                                   \
          constraint(MaxHeapSizeConstraintFunc,AfterErgo)                   \
```

ScaleForWordSize は以下で定義。

```cpp:share/runtime/globals.hpp
#ifdef _LP64
#define ScaleForWordSize(x) align_down_((x) * 13 / 10, HeapWordSize)
#else
#define ScaleForWordSize(x) (x)
#endif
```

よって、MaxHeapSize は 96 * 1.3 = 124.8MB
1.3 は32bitに比べてポインタサイズが倍になる分を考慮したものと思われる。

## set_heap_size()

MaxHeapSize InitialHeapSizeのデフォルト値はほぼarguments.cppのset_heap_size()で決定される。

```cpp:share/runtime/arguments.cpp
void Arguments::set_heap_size() {
  julong phys_mem =
    FLAG_IS_DEFAULT(MaxRAM) ? MIN2(os::physical_memory(), (julong)MaxRAM)
                            : (julong)MaxRAM;
```

MaxRAM の指定があれば、MaxRAM
指定がなければ MaxRAM(デフォルト 1G) or 物理メモリの小さい方、を phys_mem とする。

```cpp:set_heap_size()
  // Convert deprecated flags
  if (FLAG_IS_DEFAULT(MaxRAMPercentage) &&
      !FLAG_IS_DEFAULT(MaxRAMFraction))
    MaxRAMPercentage = 100.0 / MaxRAMFraction;

  if (FLAG_IS_DEFAULT(MinRAMPercentage) &&
      !FLAG_IS_DEFAULT(MinRAMFraction))
    MinRAMPercentage = 100.0 / MinRAMFraction;

  if (FLAG_IS_DEFAULT(InitialRAMPercentage) &&
      !FLAG_IS_DEFAULT(InitialRAMFraction))
    InitialRAMPercentage = 100.0 / InitialRAMFraction;
```

MaxRAMFraction MinRAMFraction InitialRAMFraction はdeprecatedになりMaxRAMPercentage MinRAMPercentage InitialRAMFraction に置き換えられた。
古いオプションで指定された場合、ここで新しいオプションに変換する。
従来はメモリの何分の1という指定方法だったが、Java11ではメモリの何パーセントという指定になっている。

```cpp:set_heap_size()
  // If the maximum heap size has not been set with -Xmx,
  // then set it as fraction of the size of physical memory,
  // respecting the maximum and minimum sizes of the heap.
  if (FLAG_IS_DEFAULT(MaxHeapSize)) {
    julong reasonable_max = (julong)((phys_mem * MaxRAMPercentage) / 100);
    const julong reasonable_min = (julong)((phys_mem * MinRAMPercentage) / 100);
    if (reasonable_min < MaxHeapSize) {
      // Small physical memory, so use a minimum fraction of it for the heap
      reasonable_max = reasonable_min;
    } else {
      // Not-small physical memory, so require a heap at least
      // as large as MaxHeapSize
      reasonable_max = MAX2(reasonable_max, (julong)MaxHeapSize);
    }
```

MaxHeapSize 指定なしで、メモリが248M(MaxHeapSize  / (MinRAMPercentage/100) )以下の場合
  reasonable_max = メモリ * 0.5
そうでなければ
  reasonable_max = メモリ * 0.25  or 126M の大きい方 
となる。
reasonable_max は後(★の箇所)に MaxHeapSize として設定される値。

```cpp:set_heap_size()
    if (!FLAG_IS_DEFAULT(ErgoHeapSizeLimit) && ErgoHeapSizeLimit != 0) {
      // Limit the heap size to ErgoHeapSizeLimit
      reasonable_max = MIN2(reasonable_max, (julong)ErgoHeapSizeLimit);
    }
```

ErgoHeapSizeLimit はデフォルト0
ErgoHeapSizeLimit 指定ありでreasonable_max より小さい場合、reasonable_max に ErgoHeapSizeLimit をセット
ErgoHeapSizeLimit の用途は不明。ここでしか使われていない。

```cpp:set_heap_size()
    if (UseCompressedOops) {
      // Limit the heap size to the maximum possible when using compressed oops
      julong max_coop_heap = (julong)max_heap_for_compressed_oops();  // 32GB

      // HeapBaseMinAddress can be greater than default but not less than.
      if (!FLAG_IS_DEFAULT(HeapBaseMinAddress)) {  // HeapBaseMinAddress = 2GB
        if (HeapBaseMinAddress < DefaultHeapBaseMinAddress) {
          // matches compressed oops printing flags
          log_debug(gc, heap, coops)("HeapBaseMinAddress must be at least " SIZE_FORMAT
                                     " (" SIZE_FORMAT "G) which is greater than value given " SIZE_FORMAT,
                                     DefaultHeapBaseMinAddress,
                                     DefaultHeapBaseMinAddress/G,
                                     HeapBaseMinAddress);
          FLAG_SET_ERGO(size_t, HeapBaseMinAddress, DefaultHeapBaseMinAddress);
        }
      }

      if (HeapBaseMinAddress + MaxHeapSize < max_coop_heap) { // 2G + 124M < 32G
        // Heap should be above HeapBaseMinAddress to get zero based compressed oops
        // but it should be not less than default MaxHeapSize.
        max_coop_heap -= HeapBaseMinAddress;  // 約 30GB
      }
      reasonable_max = MIN2(reasonable_max, max_coop_heap);
    }
```

CompressedOop の処理。
UseCompressedOopsは64bitOSではデフォルトでtrueになっている。
max_heap_for_compressed_oops()はCompressedOopでのJavaヒープ最大オフセット(=32GB)を返す。(詳細は後述)
HeapBaseMinAddressがデフォルトの2GBより小さい値が指定された場合は2GBに補正。
HeapBaseMinAddress + MaxHeapSize が 32GBより小さい場合は(デフォルトでは真)
reasonable_max に 32GB - HeapBaseMinAddress (=30GB) を設定する。
これは、Javaヒープ開始アドレス(HeapBaseMinAddress)分、Javaヒープサイズを小さくする必要があるため。

```cpp:set_heap_size()
    reasonable_max = limit_by_allocatable_memory(reasonable_max);
```

これまでの処理で決定された、reasonable_maxのサイズでallocate可能かチェックし、不可の場合、可能なサイズを設定する。

```cpp:set_heap_size()
    if (!FLAG_IS_DEFAULT(InitialHeapSize)) {  // InitialHeapSize デフォルト0
      // An initial heap size was specified on the command line,
      // so be sure that the maximum size is consistent.  Done
      // after call to limit_by_allocatable_memory because that
      // method might reduce the allocation size.
      reasonable_max = MAX2(reasonable_max, (julong)InitialHeapSize);
    }
```

InitialHeapSize が設定されていたらreasonable_maxはreasonable_maxとInitialHeapSizeの大きい方の値をセット。
MaxHeapSize がInitialHeapSizeより小さいのは矛盾するため。

```cpp:set_heap_size()
    log_trace(gc, heap)("  Maximum heap size " SIZE_FORMAT, (size_t) reasonable_max);
    FLAG_SET_ERGO(size_t, MaxHeapSize, (size_t)reasonable_max);
  }
```

これまでの処理で決定されたreasonable_maxをMaxHeapSizeとして設定する。(★)

```cpp:set_heap_size()
  // If the minimum or initial heap_size have not been set or requested to be set
  // ergonomically, set them accordingly.

  if (InitialHeapSize == 0 || min_heap_size() == 0) {
    julong reasonable_minimum = (julong)(OldSize + NewSize);

    reasonable_minimum = MIN2(reasonable_minimum, (julong)MaxHeapSize);

    reasonable_minimum = limit_by_allocatable_memory(reasonable_minimum);

    if (InitialHeapSize == 0) {
      julong reasonable_initial = (julong)((phys_mem * InitialRAMPercentage) / 100);

      reasonable_initial = MAX3(reasonable_initial, reasonable_minimum, (julong)min_heap_size());
      reasonable_initial = MIN2(reasonable_initial, (julong)MaxHeapSize);

      reasonable_initial = limit_by_allocatable_memory(reasonable_initial);

      log_trace(gc, heap)("  Initial heap size " SIZE_FORMAT, (size_t)reasonable_initial);
      FLAG_SET_ERGO(size_t, InitialHeapSize, (size_t)reasonable_initial);
    }
    ....
```

InitialHeapSize が設定されていない場合、InitialHeapSize は 
- (メモリサイズ * InitialRAMPercentage) または、
- (OldSize + NewSize)
の大きい方(ただし、MaxHeapSizeを超えない) を設定する。


## max_heap_for_compressed_oops()

max_heap_for_compressed_oops は CompressedOopでのJavaヒープ最大オフセットである32GBを返す。

```cpp:
size_t Arguments::max_heap_for_compressed_oops() {
  // Avoid sign flip.
  assert(OopEncodingHeapMax > (uint64_t)os::vm_page_size(), "Unusual page size");
  // We need to fit both the NULL page and the heap into the memory budget, while
  // keeping alignment constraints of the heap. To guarantee the latter, as the
  // NULL page is located before the heap, we pad the NULL page to the conservative
  // maximum alignment that the GC may ever impose upon the heap.

  size_t displacement_due_to_null_page = align_up((size_t)os::vm_page_size(),
                                                  _conservative_max_heap_alignment);

  LP64_ONLY(return OopEncodingHeapMax - displacement_due_to_null_page);
  NOT_LP64(ShouldNotReachHere(); return 0);
}
```

displacement_due_to_null_page は 4K。
OopEncodingHeapMax は以下で設定。

```cpp:
void set_object_alignment() {
  ....

  LogMinObjAlignmentInBytes  = exact_log2(ObjectAlignmentInBytes);
  LogMinObjAlignment         = LogMinObjAlignmentInBytes - LogHeapWordSize;

  // Oop encoding heap max
  OopEncodingHeapMax = (uint64_t(max_juint) + 1) << LogMinObjAlignmentInBytes;
```

LogMinObjAlignmentInBytes は以下のように 8 。

```cpp:share/runtime/globals.hpp
  lp64_product(intx, ObjectAlignmentInBytes, 8,                             \
          "Default object alignment in bytes, 8 is minimum")                \
```

よって LogMinObjAlignmentInBytes は log2(8) で 3
OopEncodingHeapMax は 0x1 0000 0000 = 4GB << 3 で 32GB

HeapBaseMinAddress は 2GB

```cpp:os_cpu/linux_x86/globals_linux_x86.hpp
// Used on 64 bit platforms for UseCompressedOops base address
define_pd_global(size_t, HeapBaseMinAddress,     2*G);
```

よって max_coop_heap = 32GB - 2GB = 約30GB (実際はアラインとかあるので少し異なる)

# 実機確認

実機で PrintFlagsInitial or PrintFlagsFinalを使って上記の内容を確認した。
出力結果は適宜、省略している。メモリサイズはMaxRAMで代用している。

MaxHeapSize
MaxHeapSize の初期デフォルト値(PrintFlagsInitialを使用している)
centos7 >> java  -XX:+PrintFlagsInitial -version | grep MaxHeapSize
   size_t MaxHeapSize                              = 130862280

メモリサイズ 1024m の時、その1/4 の256m になっている。
centos7 >> java   -XX:MaxRAM=1024m -XX:+PrintFlagsFinal -version | grep MaxHeapSize
   size_t MaxHeapSize                              = 268435456

メモリサイズ 248m の時、その1/2 の124m になっている。
centos7 >> java   -XX:MaxRAM=248m -XX:+PrintFlagsFinal -version | grep MaxHeapSize
   size_t MaxHeapSize                              = 130023424

メモリサイズ 120G の時、約30G(29.97G)になっている。
centos7 >> java   -XX:MaxRAM=120G -XX:+PrintFlagsFinal -version | grep MaxHeapSize
   size_t MaxHeapSize                              = 32178700288

MaxRAMPercentageが10の時、メモリサイズ10Gの10%になっている。
centos7 >> java   -XX:MaxRAM=10G -XX:MaxRAMPercentage=10  -XX:+PrintFlagsFinal -version | grep MaxHeapSize
   size_t MaxHeapSize                              = 1073741824

InitialHeapSize
InitialHeapSizeはデフォルトでメモリサイズの1/64の1Mになっている。
centos7 >> java   -XX:MaxRAM=640M  -XX:+PrintFlagsFinal -version | grep InitialHeapSize
   size_t InitialHeapSize                          = 10485760

InitialRAMPercentageが5の時、メモリサイズ10Gの5%になっている。
centos7 >> java   -XX:MaxRAM=10G -XX:InitialRAMPercentage=5  -XX:+PrintFlagsFinal -version | grep InitialHeapSize
   size_t InitialHeapSize                          = 536870912

OldSize、NewSizeの和がInitialHeapSizeのデフォルト値より大きい場合はその値40Mになる。
centos7 >> java   -XX:MaxRAM=640M -XX:OldSize=20m -XX:NewSize=20m -XX:+PrintFlagsFinal -version | grep InitialHeapSize
   size_t InitialHeapSize                          = 41943040