一个简单的“Hello World”在64位机器上需要10G虚拟内存,而在32位机器上需要1G虚拟内存?

在我们的生产机器上运行一个简单的Java程序,我注意到这个程序消耗了更多的10G virt。我知道虚拟内存并不那么重要,但至少我想了解为什么需要这样做。

public class Main {
  public static void main(String[] args) {
        System.out.println("Hello World!");
        try {
                Thread.sleep(10000);
        } catch(InterruptedException e) {
                /* ignored */
        }
  }
}

以下是我运行该小程序时所说的内容:top

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
18764 myuser    20   0 10.2g  20m 8128 S  1.7  0.1   0:00.05 java

有谁知道为什么会发生这种情况?

uname -a 说:

Linux m4fxhpsrm1dg 2.6.32-358.18.1.el6.x86_64 #1 SMP Fri Aug 2 17:04:38 EDT 2013 x86_64 x86_64 x86_64 GNU/Linux

在较旧的32位Linux机器上,相同的程序仅消耗约1G virt。旧机器有4GB RAM,新机器有32GB。


答案 1

初始堆和最大堆的默认大小定义为计算机物理内存的百分比,如今,生产服务器往往具有大量物理内存。

您可以通过 -Xms 和 -Xmx 命令行选项同时选择这两个选项。


答案 2

虚拟内存对你来说真的无关紧要。

32 位和 64 位之间的基本区别在于 64 位中的地址空间非常大。如果 10 GiB 对您来说看起来很多,请注意,64 位上的 .NET 可以使用像这样的 TiB 内存。然而,在32位上,.NET要保守得多(JVM也是如此) - 地址空间总共为4 GiB - 这不是很多。

但这无关紧要 - 这无关紧要。这只是一个大大简化编程的事情,对主机操作系统没有任何负面影响。它为 VM 创建了一个连续的地址空间供 VM 使用,这意味着您不必对堆进行分段(或者更糟糕的是,堆栈,这或多或少是不可能的 - 但这些往往只是 MiB 左右),因为您需要更多“真实”内存。当您最终提交虚拟内存时,它会变得更加真实 - 此时,它或多或少必须由一些数据存储支持 - 无论是页面(交换)文件还是物理RAM。

关键是,内存的物理位置不一定是连续的,但这是在你力所能及的范围内完成的,并且映射通常非常快。另一方面,必须为数组编制索引,该数组实际上已分散在10个不同的虚拟地址内存块上,这是(完全不必要的)工作。

所以你有它 - 虚拟内存在64位上几乎是免费的。基本方法是“如果它在那里,就使用它”。您不会限制其他应用程序,如果您最终确实使用它,它可以为您节省相当多的工作。但在那一刻到来之前,你只有一个保留。它根本不会转换为任何物理内存。你不会为那些今晚可能来坐在你桌旁的朋友付钱,但如果他们真的来了,你仍然有空间让他们坐下——只有当他们最终来的时候,你才真正被“收费”。

有关 Java 在不同机器和不同版本上的行为方式的更多信息,请参阅此问题:来自 Java SE 6 的 Sun JVM 的默认最大堆大小是多少?最大堆大小还决定了保留的虚拟内存量,因为堆必须是连续的地址空间。如果未预先保留,则可能发生堆无法扩展到此最大值的情况,因为其他人在堆必须展开的位置保留了地址空间区域。