はじめに
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 は省略している。
product(size_t, MaxHeapSize, ScaleForWordSize(96*M), \
"Maximum heap size (in bytes)") \
constraint(MaxHeapSizeConstraintFunc,AfterErgo) \
ScaleForWordSize は以下で定義。
#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()で決定される。
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 とする。
// 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ではメモリの何パーセントという指定になっている。
// 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 として設定される値。
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 の用途は不明。ここでしか使われていない。
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ヒープサイズを小さくする必要があるため。
reasonable_max = limit_by_allocatable_memory(reasonable_max);
これまでの処理で決定された、reasonable_maxのサイズでallocate可能かチェックし、不可の場合、可能なサイズを設定する。
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より小さいのは矛盾するため。
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として設定する。(★)
// 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 。
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
// 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