为Java虚拟机分配堆内存大于机器物理内存会怎么样?
之前在某个地方看到的一个问题,“如果为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来扩大内存提高程序运行效率。
更多可访问网站:JDK源码剖析网
为Java虚拟机分配堆内存大于机器物理内存会怎么样?的更多相关文章
- java虚拟机的堆内存配置
官网文档地址:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 接录如下: -XX:MaxHeapSize=si ...
- Java虚拟机:JVM内存分代策略
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代.老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存 ...
- java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...
- 《深入理解 java 虚拟机》学习 -- 内存分配
<深入理解 java 虚拟机>学习 -- 内存分配 1. Minor GC 和 Full GC 区别 概念: 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java ...
- Java虚拟机学习 - 体系结构 内存模型
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”, 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最 ...
- Java虚拟机学习 - 体系结构 内存模型(1)
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆", 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内 ...
- java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)
概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...
- Java虚拟机学习 - 体系结构 内存模型(转载)
一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”, 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB, ...
- 深入理解Java虚拟机(自动内存管理机制)
文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...
- 实战Java虚拟机之一“堆溢出处理”
从今天开始,我会发5个关于java虚拟机的小系列: 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实战Java虚拟机之四 ...
随机推荐
- Linux嵌入式设备怎么确定网络端口的速率
Linux嵌入式设备怎么确定网络端口的速率 突发奇想,就是Linux下面我能不能查询到端口的速率,以此来判断要不要频繁的发送网络数据包呢? 或者更换包利用率更高的协议呢. 于是抱着这样的想法,我开始学 ...
- Typecho头像被墙的解决方法
首先下载最新开发版本的TYPECHO,然后,在config.inc.php自定义如下: /** 自定义gravatar url前缀 */ define('__TYPECHO_GRAVATAR_PREF ...
- C#中的StreamWriter和"谁创建谁释放"原则
C# 类库中的 StreamWriter 类在释放时会同时关闭其所依赖的基础流对象,这是为了确保所有缓冲数据都被写入基础流中,并且在不再需要 StreamWriter 对象时,基础流对象也能够被及时释 ...
- dx12学习之旅-
记录一下,第一篇博客2024年7月26日下午. 计划在毕业后从事游戏开发的工作,现在在学习龙书dx12,平时会写一些对龙书内容上的一些理解.在读完全书之后,会考虑进行一次龙书相关的总结,不过这应该要很 ...
- angular+ionic项目,页面无法滚动的问题
在做angular+ionic+cordova项目时,遇到一个小小的问题,就是内容做完,页面无法滚动,导致内容显示不完整 首先我检查了样式,发现并没有给页面定死高度,再次检查结构发现,我并没有用ion ...
- WARN Issues with peer dependencies found,pnpm peer dependencies auto-install
前言 pnpm 也需要设置自动安装对等依赖项 解决 pnpm 使用 npm 的配置格式,所以应该以与 npm 相同的方式设置配置: pnpm config set auto-install-peers ...
- 使用 vscode-jest 插件
vscode-jest [error] Abort jest session: Not able to auto detect a valid jest command: multiple candi ...
- Ubuntu安装mosquitto并进行配置
要在Ubuntu上安装Mosquitto并进行配置,你可以按照以下步骤进行操作: 打开终端. 更新软件包列表,使用以下命令: sudo apt update 安装Mosquitto包,使用以下命令: ...
- 自定义异常--java进阶day08
1.自定义异常 2.自定义异常的格式 看你想要定义哪种异常,对应的继承哪种异常类 以我们之前写的代码举例,Exception类过于庞大,所有的异常子类都可以被它接收,这样就会导致无法精确捕获,所以我们 ...
- ArrayList的常用成员方法
1.ArrayList常用成员方法 可以大致分为4种,增 删 改 查 1.增 1.public boolean add(E e) 将括号里的元素直接添加到集合中,添加的元素按照顺序依次排列. 其中,E ...