为什么-Xmx和Runtime.maxMemory不一致

30

当您添加时

 -Xmx????m

在命令行中,JVM会给你一个大小接近本值的堆,但可能有14%的偏差。JVM可以给你一个更接近所需值的数字,但只能通过试错来实现。

 System.out.println(Runtime.getRuntime().maxMemory());

打印

-Xmx1000m ->  932184064
-Xmx1024m -Xmx1g ->  954728448
-Xmx1072m ->  999292928
-Xmx1073m -> 1001390080

我正在运行HotSpot Java 8更新5。

显然,堆可以是略高于1000000000,但为什么这里是-Xmx1073m而不是-Xmx1000m呢?

顺便说一下,1g == 1024m,这表明1g应该是1024^3,比1000^3高7%,但你得到的东西比1000^3低7%。


如此大的偏差表明我在理解堆的工作原理方面缺少了某些基本知识。如果我要求-Xmx1000m,并且它是1001390080,我不会在意,我会认为它需要遵循一些分配倍数,但是给你932184064则表示对我来说堆比我想象的更复杂。


编辑 我已经发现了

-Xmx1152m gives 1073741824 which is exactly 1024^3

看起来在这种情况下,根据maxMemory()函数,它给了我比我要求的少128 MB。


顺便说一句,128是我最喜欢的数字。今天我在一个街道号码为128的会议上,演讲者引用了一本书的第128页。


3
你为什么关心这个数字恰好是“10亿”? - Absurd-Mind
2
@Absurd-Mind 这只是我多年来一直困扰的那些不精确的事情之一。在不同的JVM中也有所不同,因此您无法确定在系统之间实际上会得到什么。 - Peter Lawrey
3
“@Absurd-Mind,它不一定要完全准确,但偏差必须如此之大吗?这表明我在理解堆是如何工作方面缺少了一些基本知识。” - Peter Lawrey
1
@Alex 是的,我在同一台机器上得到了相同的数字。顺便说一下,-Xmx1g == -Xmx1024m,这使得它变得更加奇怪。 - Peter Lawrey
1
Java在Ubuntu 14.04的版本是“1.7.0_79”,使用-Xmx1152m也会产生1073741824的结果。我注意到这比1GB多了128MiB,而不是多了128MB。128MB相当于128,000,000字节。请参考https://en.wikipedia.org/wiki/Binary_prefix 了解关于Mebibyte和Gibibyte的更多信息 :) - nealmcb
显示剩余10条评论
2个回答

35
区别似乎是由垃圾回收器的幸存者空间大小所解释。-Xmx标志,如docs中所述,控制内存分配池的最大大小。memory allocation pool的堆部分被划分为Eden、Survivor和Tenured空间。正如this answer中所述,有两个幸存者区域,其中只有一个在任何给定时间可以容纳活动对象。因此,由Runtime.maxMemory()报告的可用于分配对象的总表面空间必须从总堆内存池中减去一个幸存者空间的大小。您可以使用MemoryMXBean和MemoryPoolMXBean类获得有关内存分配的更多信息。以下是我编写的一个简单程序:
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;

public class MemTest {
  static String mb (long s) {
    return String.format("%d (%.2f M)", s, (double)s / (1024 * 1024));
  }

  public static void main(String[] args) {
    System.out.println("Runtime max: " + mb(Runtime.getRuntime().maxMemory()));
    MemoryMXBean m = ManagementFactory.getMemoryMXBean();

    System.out.println("Non-heap: " + mb(m.getNonHeapMemoryUsage().getMax()));
    System.out.println("Heap: " + mb(m.getHeapMemoryUsage().getMax()));

    for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) {
      System.out.println("Pool: " + mp.getName() + 
                         " (type " + mp.getType() + ")" +
                         " = " + mb(mp.getUsage().getMax()));
    }
  }
}

在OpenJDK 7上运行java -Xmx1024m MemTest的输出结果是:

Runtime max: 1037959168 (989.88 M)
Non-heap: 224395264 (214.00 M)
Heap: 1037959168 (989.88 M)
Pool: Code Cache (type Non-heap memory) = 50331648 (48.00 M)
Pool: Eden Space (type Heap memory) = 286326784 (273.06 M)
Pool: Survivor Space (type Heap memory) = 35782656 (34.13 M)
Pool: Tenured Gen (type Heap memory) = 715849728 (682.69 M)
Pool: Perm Gen (type Non-heap memory) = 174063616 (166.00 M)

请注意,Eden + 2 * Survivor + Tenured = 1024M,这正是在命令行中请求的堆空间量。非常感谢@Absurd-Mind指出这一点。
您观察到不同JVM之间的差异很可能是由于选择各种代的默认相对大小的启发式方法不同。如此文章所述(适用于Java 6,未能找到更近期的文章),您可以使用-XX:NewRatio-XX:SurvivorRatio标志来明确控制这些设置。因此,请运行以下命令:
java -Xmx1024m -XX:NewRatio=3 -XX:SurvivorRatio=6

您正在告诉JVM:
Young:Tenured = (Eden + 2*Survivor):Tenured = 1:3 = 256m:768m
Survivor:Eden = 1:6 = 32m:192m

因此,根据这些参数,请求的-Xmx值与Runtime.maxMemory()报告的可用内存之间的差异应为32m,并使用上述程序进行验证。现在,您应该能够准确预测给定一组命令行参数时Runtime报告的可用内存,这正是您想要的,对吗?

5
如果你将所有的堆空间加起来并将Survivor(幸存者)乘以2,你会得到准确的1024m。伊甸园(Eden)+ 2个幸存者(Survivor)+ 老年代(Tenured): 715849728 + 2 * 35782656 + 286326784 = 1073741824 = 1024M。也许这个空间以某种方式被复制了? - Absurd-Mind
哇,好棒的提问。我一直在寻找让数字相加的方法,但是一直没有成功。https://dev59.com/rGgv5IYBdhLWcg3wTvE- - Alex
我查看了JVM源代码,堆大小将根据页面大小对齐,因此仍可能存在一些差异。在我的机器上(Windows7,x64,Oracle JDK 1.7),堆大小只能以512k的步长设置。 - Absurd-Mind
是的,我不感到意外。我只见过人们将-Xmx设置为1m的倍数,因此在大多数情况下,我不认为这会成为一个问题。 - Alex
顺便提一下,在没有设置比率的情况下对程序输出进行数学计算表明,在我的平台(Ubuntu x64,OpenJDK 7 u51)上的默认比率为 NewRatio=2SurvivorRatio=8 - Alex
太棒了!在几乎所有方面都非常清晰,但是因为你混淆了十进制和二进制的幂次,所以有些令人困惑。由于你正在计算的是Mebibytes而不是Megabytes,所以应该将“%.2f M”更改为“%.2f MiB”,如https://en.wikipedia.org/wiki/Binary_prefix中所讨论的那样。 - nealmcb

0
以下图表显示了不同Xmx值(x轴)的Runtime.maxMemory作为Xmx百分比(y轴)。
我们可以看到,大部分情况下只有Xmx设置的约85%可用于堆的增长。此分析使用java版本“1.8.0_212” Java(TM) SE Runtime Environment (build 1.8.0_212-b10) Java HotSpot(TM) 64-Bit Server VM (build 25.212-b10, mixed mode)进行。 不同Xmx大小(以MB为单位)的(maxMem/Xmx)图表

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