写点什么

为 Java 虚拟机分配堆内存大于机器物理内存会怎么样?

  • 2025-05-12
    福建
  • 本文字数:1831 字

    阅读完需:约 6 分钟

之前在某个地方看到的一个问题,“如果为 Java 虚拟机指定的堆内存大于物理内存会怎么样?”,今天正好又看到了 HotSpot VM 中关于为堆分配内存的源代码实现,顺便从源代码角度解答一下这个问题。

我们平时为堆分配内存时,会调用到 os::reserve_memory()函数,这个函数的实现如下:


char* os::reserve_memory(  size_t bytes, char* addr, size_t alignment_hint) {  char* result = pd_reserve_memory(bytes, addr, alignment_hint);  return result;} char* os::pd_reserve_memory(  size_t bytes, char* requested_addr,size_t alignment_hint) {  return anon_mmap(requested_addr, bytes, (requested_addr != NULL));} 
复制代码


调用的 anon_mmap()函数的实现如下:


源代码位置:openjdk/hotspot/src/os/linux/vm/os_linux.cpp // 如果参数fixed为true,则要求分配的内存基址从requested_addr开始,如果这个内存基地被// 占用,则会发生重写,我们不对基址有要求,所以fixed的值为false,requested_addr的值// 为NULL,如果有值的话,内存基址可能会从从requested_addr开始,不过这不是必须的static char* anon_mmap(char* requested_addr, size_t bytes, bool fixed) {  char * addr;  int flags;     flags = MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS;  if (fixed) {    flags |= MAP_FIXED;  }     addr = (char*)::mmap(requested_addr, bytes, PROT_NONE,flags, -1, 0);     ...     return addr == MAP_FAILED ? NULL : addr;}
复制代码


默认情况下,内核会为匿名映射(如使用 MAP_ANONYMOUS 创建的映射)预先分配交换空间,确保物理内存不足时将数据换出。而 MAP_NORESERVE 会跳过此预留步骤,允许进程分配大于当前 可用物理内存+交换空间总和的内存区域。


我们看一下我本地机器的可用物理内存和交换空间的大小:



物理可用内存 5.8G,Swap 是 4G

这里需要解释一下 MAP_NORESERVE,表示“不申请交换空间”。由于 Linux 申请内存是两阶段提交,阶段一是申请到虚拟内存,当有访问到虚拟内存时才会触发第二阶段,为虚拟内存分配对应的物理内存。这里不申请交换空间,因为是处在阶段一,申请交换空间是一种浪费。


对于第一阶段的内存申请,由于申请的是虚拟内存,实际上 64 位操作系统,进程可以使用 128 TB 大小的虚拟内存空间,所以进程申请一个远大于本机物理内存是没问题的,只要不读写这个虚拟内存,操作系统就不会分配物理内存。


假设调用 anon_map()函数分配 500G,实例如下:


// 1G内存大小size_t length = 1UL * 1024 * 1024 * 1024;char*  c = anon_mmap(NULL,length * 500 ,false);此时使用如下命令查看这个进程分配的虚拟空间:
ps aux | grep -E "VSZ|test"
复制代码


如下所示。



其中的 VSZ 显示了进程的虚拟地址空间大小为 500G。这个内存已经远远大于了物理内存的大小了。


对于第二阶段来说,我们到底可以使用多大的物理内存呢?这要介绍一下 Swap。


当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间会被临时保存到磁盘,等到那些程序要运行时,再从磁盘中恢复保存的数据到内存中。


另外,当内存使用存在压力的时候,会开始触发内存回收行为,会把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。


这种,将内存数据换出磁盘,又从磁盘中恢复数据到内存的过程,就是 Swap 机制负责的。


我们先使用如下命令关闭 Swap,然后为虚拟机分配堆的大小为 6G,实际上可用的物理内存是 5.8G,所以不出意外的,内存分配失败了。



这里在启动虚拟机时,添加了-XX:+AlwaysPreTouch 参数,这个参数会按页访问内存,这样就能为虚拟内存分配对应的物理内存了。


我们现在开启 Swap 后,再为虚拟机分配 6G 堆大小,如下:



可以看到,程序运行成功。再看内存情况后会看到 Swap 使用了 200 多 M 的内存。

 

实际上不能太多的使用 Swap,否则磁盘换入换出,整个系统会非常卡。所以 Swap 可以看成一种保障,一定程度上可保障在内存吃紧时不会杀掉进程,但是如果虚拟机开始使用 Swap,通常会造成性能明显下降,由 Swap 引起的性能问题也不算少。


 所以我们可不能认为,当内存敏感型的程序上线后,如果内存不足,可借用 Swap 来扩大内存提高程序运行效率。


文章转载自:鸠摩(马智)

原文链接:https://www.cnblogs.com/mazhimazhi/p/18868935

体验地址:http://www.jnpfsoft.com/?from=001YH

用户头像

还未添加个人签名 2025-04-01 加入

还未添加个人简介

评论

发布
暂无评论
为Java虚拟机分配堆内存大于机器物理内存会怎么样?_Java_电子尖叫食人鱼_InfoQ写作社区