JVM是一个运行在操作系统上的虚拟机。因为是一台虚拟的机器,所以对于内存肯定是可以控制的,恰好JVM提供了众多个参数控制内部运行时,而Xms和Xmx两个参数是用来控制Java堆内存的最小内存和最大内存。可能不少读者会好奇Xms和Xmx两个参数能够设置的最小值是多少,所以笔者借助源码来简单论述~
答案先放在这里
JDK8
-Xms >= 1MB
-Xmx >= -Xms
JDK12
-Xms >= 1MB
-Xmx >= 2MB
版本信息:
jdk版本:jdk8u40
jdk版本:jdk12u
为什么会写两个JDK版本呢,因为笔者认为JDK8版本写的很乱,逻辑很差,而在JDK12版本及时改正。
因为Xms和Xmx是两个JVM参数,所以第一步需要看到JVM如何解析参数。/src/share/vm/runtime/arguments.cpp 文件中
// -Xms
else if (match_option(option, "-Xms", &tail)) {
julong long_initial_heap_size = 0;
// 解析-Xms参数
// 不能小于0
// 如果等于0,代表交给JVM自动分配
ArgsRange errcode = parse_memory_size(tail, &long_initial_heap_size, 0);
// 设置最小大小
set_min_heap_size((uintx)long_initial_heap_size);
// 如果没有设置-XX:InitialHeapSize参数的话,堆的初始值就为最小值。
// 相反,如果设置了-XX:InitialHeapSize参数的话,堆的初始值就发生改变。
FLAG_SET_CMDLINE(uintx, InitialHeapSize, (uintx)long_initial_heap_size);
// -Xmx
} else if (match_option(option, "-Xmx", &tail) || match_option(option, "-XX:MaxHeapSize=", &tail)) {
julong long_max_heap_size = 0;
// 解析-Xmx参数
// 不能小于1,当然,这里可以理解为仅仅是解析出用户设置的大小。
ArgsRange errcode = parse_memory_size(tail, &long_max_heap_size, 1);
FLAG_SET_CMDLINE(uintx, MaxHeapSize, (uintx)long_max_heap_size);
}
代码非常的简单,解析参数,然后设置参数,而解析参数都是调用的parse_memory_size方法,所以略微的看一下解析过程。
Arguments::ArgsRange Arguments::parse_memory_size(const char* s,
julong* long_arg,
julong min_size) {
if (!atomull(s, long_arg)) return arg_unreadable;
return check_memory_size(*long_arg, min_size);
}
// src/share/vm/utilities/globalDefinitions.cpp 中的宏定义,用来定义内存大小,比如k、m、g
const size_t K = 1024;
const size_t M = K*K;
const size_t G = M*K;
const size_t HWperKB = K / sizeof(HeapWord);
// 代码非常简单,把Xms参数的数值部分和内存单位分别解析出来,然后计算出最终大小
static bool atomull(const char *s, julong* result) {
julong n = 0;
int args_read = sscanf(s, JULONG_FORMAT, &n);
if (args_read != 1) {
return false;
}
while (*s != '\0' && isdigit(*s)) {
s++;
}
// 4705540: illegal if more characters are found after the first non-digit
if (strlen(s) > 1) {
return false;
}
switch (*s) {
case 'T': case 't':
*result = n * G * K;
// Check for overflow.
if (*result/((julong)G * K) != n) return false;
return true;
case 'G': case 'g':
*result = n * G;
if (*result/G != n) return false;
return true;
case 'M': case 'm':
*result = n * M;
if (*result/M != n) return false;
return true;
case 'K': case 'k':
*result = n * K;
if (*result/K != n) return false;
return true;
case '\0':
*result = n;
return true;
default:
return false;
}
}
这里完全是解析,其实并没有做啥限制,解析后的数据存入到全局的变量中。接下来我们看设置Java堆空间的部分源码。/src/share/vm/runtime/arguments.cpp 文件中
void Arguments::set_heap_size() {
// 得到当前机器的物理内存大小
const julong phys_mem =
FLAG_IS_DEFAULT(MaxRAM) ? MIN2(os::physical_memory(), (julong)MaxRAM)
: (julong)MaxRAM;
// 如果没有使用Xmx设置Java堆大小,就使用默认实现
// 下面的默认实现,是物理内存的1/4.
if (FLAG_IS_DEFAULT(MaxHeapSize)) {
// 拿到物理内存的1/4
julong reasonable_max = phys_mem / MaxRAMFraction;
if (phys_mem <= MaxHeapSize * MinRAMFraction) {
reasonable_max = phys_mem / MinRAMFraction;
} else {
reasonable_max = MAX2(reasonable_max, (julong)MaxHeapSize);
}
reasonable_max = limit_by_allocatable_memory(reasonable_max);
// 如果使用参数设置了Java堆的初始化大小。
if (!FLAG_IS_DEFAULT(InitialHeapSize)) {
// 如果Java堆的初始化大小 要大于Java堆的最大大小,就使用Java堆的初始化大小
reasonable_max = MAX2(reasonable_max, (julong)InitialHeapSize);
}
// 把最终得到的Java堆最大大小设置到MaxHeapSize中,方便后续使用。
FLAG_SET_ERGO(uintx, MaxHeapSize, (uintx)reasonable_max);
}
// 如果Java堆的初始化大小设置的为0,或者没有设置。
if (InitialHeapSize == 0 || min_heap_size() == 0) {
// 默认老年代为4mb,年轻代1mb。
julong reasonable_minimum = (julong)(OldSize + NewSize);
// 这里就是问题所在
// 如果通过Xmx设置的Java堆最大大小如果小于5mb,那么就会使用Xmx设置的Java堆最大大小。
reasonable_minimum = MIN2(reasonable_minimum, (julong)MaxHeapSize);
reasonable_minimum = limit_by_allocatable_memory(reasonable_minimum);
// 如果初始化大小没设置,那就设置
if (InitialHeapSize == 0) {
julong reasonable_initial = phys_mem / InitialRAMFraction;
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);
FLAG_SET_ERGO(uintx, InitialHeapSize, (uintx)reasonable_initial);
}
// 如果最小大小没设置,那就设置
if (min_heap_size() == 0) {
set_min_heap_size(MIN2((uintx)reasonable_minimum, InitialHeapSize));
}
}
}
这里的逻辑稍显复杂,但是仔细阅读起来,其实也并没有那么复杂。实际上,这里是对Java堆的初始化大小,最小大小,最大大小做最后的计算(不考虑到对齐)
这里仅仅是计算出大小,并没有开辟空间,所以需要看到开辟空间之前对计算出来的大小做判断的逻辑。/src/share/vm/memory/collectorPolicy.cpp 文件中。
可以很清楚的看到,都是不能小于1MB。并且最大大小只需要大于初始化大小和最小大小即可。
if (FLAG_IS_CMDLINE(MaxHeapSize)) {
// 如果初始化大小 大于 最大大小,那就抛出异常
if (FLAG_IS_CMDLINE(InitialHeapSize) && InitialHeapSize > MaxHeapSize) {
vm_exit_during_initialization("Initial heap size set to a larger value than the maximum heap size");
}
// 如果最小大小 大于 最大大小,那就抛出异常
if (_min_heap_byte_size != 0 && MaxHeapSize < _min_heap_byte_size) {
vm_exit_during_initialization("Incompatible minimum and maximum heap sizes specified");
}
_max_heap_size_cmdline = true;
}
// 最小的限制
if (InitialHeapSize < M) {
vm_exit_during_initialization("Too small initial heap");
}
if (_min_heap_byte_size < M) {
vm_exit_during_initialization("Too small minimum heap");
}
-Xms >= 1MB
-Xmx >= -Xms
彩蛋:其实,如果能够看懂源码的读者,因为计算的逻辑比较复杂,所以存在很多极端的情况,比如,只设置了-Xmx,没有设置-Xms。此时-Xmx的参数也必须大于等于1MB。
可以看到在 src/hotspot/share/gc/shared/collectorPolicy.cpp 文件中。 对于Java堆的最大和最小参数的最小值做了很直接的判断。
-Xms >= 1MB
-Xmx >= 2MB
// Check heap parameter properties
if (MaxHeapSize < 2 * M) {
vm_exit_during_initialization("Too small maximum heap");
}
if (InitialHeapSize < M) {
vm_exit_during_initialization("Too small initial heap");
}
if (_min_heap_byte_size < M) {
vm_exit_during_initialization("Too small minimum heap");
}