为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虚拟机之四 ...
随机推荐
- AI 艺术工具通讯
创刊号 AI 领域的发展速度令人惊叹,回想一年前我们还在为生成正确手指数量的人像而苦苦挣扎的场景,恍如隔世 . 过去两年对开源模型和艺术创作工具而言具有里程碑意义.创意表达的 AI 工具从未像现在这般 ...
- Gradle的安装及换源详细教程
Gradle是一个基于JVM的构建工具,用于自动化构建.测试和部署项目. 1. 安装Gradle a. 首先,确保你已经安装了Java Development Kit (JDK),并且已经配置了JAV ...
- RestClient C# 举例 是用jsonbody ,并列出httpclient 等价的方式
以下是使用 RestSharp 发送 POST 请求并附带 JSON 请求体的示例,以及相应的使用 HttpClient 的等价方式: 首先,使用 RestSharp: using System; u ...
- C++17 Filesystem 实用教程
点击查看代码 C++17 标准带来了 std::filesystem库, 提供了强大的工具来处理文件路径, 目录以及其他与文件系统相关的操作. 这篇文章适合 C++ 初学者以及希望掌握 C++17 新 ...
- pandas 如何移动列的位置
实现效果 原来备注列在第二列 代码: mid=df['备注'] #取备注列的值 df.pop('备注') #删除备注列 df.insert(4,'备注',mid) #插入备注列
- tsconfig.json 报错问题解决
tsconfig.json 报错问题解决 报错如图所示: 创建tsconfig.json配置文件时,VSCode会自动检测当前项目当中是否有ts文件,若没有则报错,提示用户需要创建一个ts文件后,再去 ...
- k8s NotReady cni config uninitialized
前言 k8s node 节点 join master 后,状态报错:NOT READY 查看 kubelet 日志 journalctl -xeu kubelet 报错如下:Container run ...
- SQLSTATE[HY000] [2002] Connection refused报错 PHP连接docker容器中的mysql
Laradock 是基于 Docker 提供的完整 PHP 本地开发环境 在框架中连接 MySQL 时 报错 SQLSTATE[HY000] [2002] Connection refused 主要还 ...
- vSphere是什么,你了解么?
最近这两周都在学习VMware vSphere相关知识,昨天在做了一个项目后,VMware虚拟化之旅暂告一段落了.晚上一个人闲下来时回想了之前所学,忆起vSphere时,大脑一片空白... 我突然发现 ...
- 深入掌握Map的这8个操作方法,让代码更简洁优雅
Map 是我们经常使用的数据结构接口,它的子类 HashMap.ConcurrentHashMap 也是我们使用比较频繁的集合. 了解了 Map 接口中的方法,也就相当于知道了其子类中的可用方法,管它 ...