为 Java 虚拟机分配堆内存大于机器物理内存会怎么样?
之前在某个地方看到的一个问题,“如果为 Java 虚拟机指定的堆内存大于物理内存会怎么样?”,今天正好又看到了 HotSpot VM 中关于为堆分配内存的源代码实现,顺便从源代码角度解答一下这个问题。
我们平时为堆分配内存时,会调用到 os::reserve_memory()函数,这个函数的实现如下:
调用的 anon_mmap()函数的实现如下:
默认情况下,内核会为匿名映射(如使用 MAP_ANONYMOUS 创建的映射)预先分配交换空间,确保物理内存不足时将数据换出。而 MAP_NORESERVE 会跳过此预留步骤,允许进程分配大于当前 可用物理内存+交换空间总和的内存区域。
我们看一下我本地机器的可用物理内存和交换空间的大小:

物理可用内存 5.8G,Swap 是 4G
这里需要解释一下 MAP_NORESERVE,表示“不申请交换空间”。由于 Linux 申请内存是两阶段提交,阶段一是申请到虚拟内存,当有访问到虚拟内存时才会触发第二阶段,为虚拟内存分配对应的物理内存。这里不申请交换空间,因为是处在阶段一,申请交换空间是一种浪费。
对于第一阶段的内存申请,由于申请的是虚拟内存,实际上 64 位操作系统,进程可以使用 128 TB 大小的虚拟内存空间,所以进程申请一个远大于本机物理内存是没问题的,只要不读写这个虚拟内存,操作系统就不会分配物理内存。
假设调用 anon_map()函数分配 500G,实例如下:
如下所示。

其中的 VSZ 显示了进程的虚拟地址空间大小为 500G。这个内存已经远远大于了物理内存的大小了。
对于第二阶段来说,我们到底可以使用多大的物理内存呢?这要介绍一下 Swap。
当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间会被临时保存到磁盘,等到那些程序要运行时,再从磁盘中恢复保存的数据到内存中。
另外,当内存使用存在压力的时候,会开始触发内存回收行为,会把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。
这种,将内存数据换出磁盘,又从磁盘中恢复数据到内存的过程,就是 Swap 机制负责的。
我们先使用如下命令关闭 Swap,然后为虚拟机分配堆的大小为 6G,实际上可用的物理内存是 5.8G,所以不出意外的,内存分配失败了。

这里在启动虚拟机时,添加了-XX:+AlwaysPreTouch 参数,这个参数会按页访问内存,这样就能为虚拟内存分配对应的物理内存了。
我们现在开启 Swap 后,再为虚拟机分配 6G 堆大小,如下:

可以看到,程序运行成功。再看内存情况后会看到 Swap 使用了 200 多 M 的内存。
实际上不能太多的使用 Swap,否则磁盘换入换出,整个系统会非常卡。所以 Swap 可以看成一种保障,一定程度上可保障在内存吃紧时不会杀掉进程,但是如果虚拟机开始使用 Swap,通常会造成性能明显下降,由 Swap 引起的性能问题也不算少。
所以我们可不能认为,当内存敏感型的程序上线后,如果内存不足,可借用 Swap 来扩大内存提高程序运行效率。
文章转载自:鸠摩(马智)
评论