Help us understand the problem. What is going on with this article?

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 は省略している。

share/gc/shared/gc_globals.hpp
  product(size_t, MaxHeapSize, ScaleForWordSize(96*M),                      \
          "Maximum heap size (in bytes)")                                   \
          constraint(MaxHeapSizeConstraintFunc,AfterErgo)                   \

ScaleForWordSize は以下で定義。

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()で決定される。

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 とする。

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ではメモリの何パーセントという指定になっている。

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 として設定される値。

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 の用途は不明。ここでしか使われていない。

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ヒープサイズを小さくする必要があるため。

set_heap_size()
    reasonable_max = limit_by_allocatable_memory(reasonable_max);

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

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より小さいのは矛盾するため。

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として設定する。(★)

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を返す。

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 は以下で設定。

void set_object_alignment() {
  ....

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

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

LogMinObjAlignmentInBytes は以下のように 8 。

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

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away