java -Xmx1G的意思是10^9字节还是2^30字节?

48
总的来说,-Xmx-Xms-Xmn选项使用的单位(“k”、“M”和“G”,或较少用的“K”、“m”或“g”)是二进制前缀倍数(即1024的幂),还是1000的幂?
手册上说它们表示千字节(kB)、兆字节(MB)和千兆字节(GB),这表明它们是原始国际单位制中定义的1000的幂。我的非正式测试(我不是很有信心)表明它们实际上是基比字节(KiB)米比字节(MiB)吉比字节(GiB),都是1024的幂。
那么哪一个才是正确的?例如,什么Java代码可以显示当前大小?
对于内存大小使用1024的倍数并不奇怪,因为内存通常是通过加倍硬件模块来物理布局的。但随着我们越来越接近更大的幂,使用清晰和标准的单位就变得越来越重要,因为混淆的可能性也随之增加。我的JVM还接受“t”这个单位,1 TiB比1 TB大10%。
注意:如果这些确实是二进制倍数,我建议更新文档和用户界面,非常明确地说明这一点,并提供如“附加字母k或K以表示基比字节(1024字节),或m或M以表示米比字节(1048576字节)”等示例。例如,在Ubuntu中采用了这种方法:UnitsPolicy - Ubuntu Wiki
注意:有关选项用于什么的更多信息,请参见java - What are the Xms and Xmx parameters when starting JVMs?

@ElliottFrisch 主要是我在提问,寻找一个明确的答案。文档建议只是为了更清楚地解释我所困惑的问题。 - nealmcb
3个回答

69
短答案:JVM命令行参数使用的所有内存大小都是以传统二进制单位指定的,其中1千字节为1024字节,其他单位是1024的幂次方。
长答案:这个命令行参数的文档页面说所有接受内存大小的参数都适用以下规则:
例如,要将大小设置为8 GB,可以指定8g8192m8388608k8589934592作为参数。
对于-Xmx,它给出了以下具体示例:
以下示例显示如何使用各种单位将分配的内存的最大允许大小设置为80 MB: -Xmx83886080 -Xmx81920k -Xmx80m

在我想要查看文档之前(我以为你已经查看了?),我检查了HotSpot的源代码,并发现内存值由函数atomull(似乎代表“ASCII转内存,无符号长整型”)在src/share/vm/runtime/arguments.cpp中解析:

// Parses a memory size specification string.
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;
  }
}

那些常量 KMGsrc/share/vm/utilities/globalDefinitions.hpp 中被定义:
const size_t K                  = 1024;
const size_t M                  = K*K;
const size_t G                  = M*K;

所有这些都得到了证实,除了对于以“T”结尾的表示“terabytes”的支持显然是后来添加的,并且根本没有记录。
不使用单位乘数也是可以的,所以如果你想要“十亿字节”,你可以写成-Xmx1000000000。如果你使用了乘数,它们是二进制的,因此-Xmx1G表示230字节,或者一根内存条。
(这并不奇怪,因为Java早于IEC试图追溯性地重新定义现有单词。如果IEC仅建议在偶尔意义不明确时用限定词“二进制”和“十进制”来消除记忆单元的歧义,混淆可能已经得到解决。例如,“二进制千兆字节”(GB2)=10243字节,“十进制千兆字节”(GB10)=10003字节。但是,他们重新定义了每个人已经在使用的单词,不可避免地引起了混淆,并让我们困在这些小丑术语“gibibyte”、“tebibyte”和其他东西中。哦,上帝饶恕我们吧。)

5
感谢找到这段代码!非常清晰易懂。 但我必须指出,是计算机专业人员重新定义了曾经超过两个世纪的明确术语。对IEC来说,保持它们长期以来的使用方式,并为新单位提供新术语,对我来说都是合理的。 MiB比“二进制兆字节”或“MB_2”更简洁,更易于格式化、翻译等。 - nealmcb
感谢您找到那份文档。它比我在我的系统(Ubuntu)和网络搜索中找到的文档更清晰地解释了MB和GB的使用,提供了更明确的示例。看起来他们对此进行了澄清,也许是针对Java 8 :) - nealmcb
1
对于Oracle Java 8来说,情况有些奇怪。设置“-Xmx8g”最终只能得到7.11 GiB(7635730432字节)的maxMemory...但是在Java 11中,我可以获得完整的8GiB。 - Robert

6

您有两种获取问题答案的选项:

a)检查JDK源代码。很抱歉我在5分钟内无法通过谷歌找到答案。

b)编写一个模拟,运行几次并进行一些观察。

public class A {
  public static void main(String[] args) throws Exception {
    System.out.println("total: " + Runtime.getRuntime().totalMemory());
  }
}

然后多次运行它:

java -Xms130m -Xmx2G A
total: 131072000
java -Xms131m -Xmx2G A
total: 132644864
java -Xms132m -Xmx2G A
total: 132644864
java -Xms133m -Xmx2G A
total: 134742016
java -Xms134m -Xmx2G A
total: 134742016

我的猜测是Java使用的不是您请求的确切数字,而是2^n的近似值。

1

你所引用的问题非常准确!特别是在那里查看Alex编写的MemTest类,以全面了解非堆内存(包括代码缓存和Perm gen池)与堆内存(包括Eden Space、Survivor space和Tenured gen池)的完整情况。 - nealmcb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接