JVM源码剖析之-Xms和-Xmx参数最小的设置值是多少

JVM是一个运行在操作系统上的虚拟机。因为是一台虚拟的机器,所以对于内存肯定是可以控制的,恰好JVM提供了众多个参数控制内部运行时,而Xms和Xmx两个参数是用来控制Java堆内存的最小内存和最大内存。可能不少读者会好奇Xms和Xmx两个参数能够设置的最小值是多少,所以笔者借助源码来简单论述~

答案先放在这里

JDK8

        -Xms >= 1MB

        -Xmx >= -Xms

JDK12

        -Xms >= 1MB

        -Xmx >= 2MB

版本信息:

jdk版本:jdk8u40
jdk版本:jdk12u

JDK8版本源码

为什么会写两个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堆的初始化大小,最小大小,最大大小做最后的计算(不考虑到对齐)

  1. 如果没有使用了Xmx设置Java堆最大大小,默认为物理内存的1/4,反之,如果设置了那就使用用户使用Xmx设置的大小
  2. 基于第一点,如果默认为物理内存的1/4要小于用户设置的Java堆初始化大小,那就使用用户设置的Java堆初始化大小
  3. 如果用户没有使用参数设置Java堆初始化大小,和最小大小。那就对其使用默认配置。默认最小大小和初始化大小为老年代4MB、年轻代1MB(可能,很多读者很诧异,为什么这么小?笔者认为,框架是适用于大众的,不能太大,不能太小。并且这里只是初始化大小)
  4. 基于第三点,如果用户设置了Java堆的最大大小,并且小于默认的初始化配置大小,那么就会使用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。

JDK12版本源码

可以看到在 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");
}

你可能感兴趣的:(Java底层,源码解读,jvm,java)