为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虚拟机之四 ...
随机推荐
- BUUCTF-Web方向16-20wp
[极客大挑战 2019]PHP 由内容提示应该存在源码备份,常见的如下,一个个尝试 后缀:tar tar.gz zip rar 名字:www web website backup back wwwro ...
- DeepSeek本地部署
一.ollama ollama是一个管理和运行所有大模型.开源大模型的平台.在官网的Models中可以看到deepseek-r1的AI模型 1.在官网中下载对应系统的ollama,下载时需要开墙,或者 ...
- MOS管的寄生电容
我们经常看到,在电源电路中,功率MOS管的G极经常会串联一个小电阻,几欧姆到几十欧姆不等,那么这个电阻用什么作用呢? 这个电阻的作用有2个作用:限制G极电流,抑制振荡. 限制G极电流MOS管是由电压驱 ...
- nginx出现: [error] open() "/usr/local/nginx/logs/nginx.pid" failed错误
问题情况 登陆服务器之后进到nginx使用./nginx -s reload重新读取配置文件,发现报==nginx: [error] open() "/usr/local/nginx/log ...
- [Jaav SE/程序生命周期] 优雅的Java应用程序的启停钩子框架
序 了解 spring 生态及框架的 java er 都知道,spring 应用的生命周期管理及配套接口较为优雅.可扩展. 但脱离 spring 的 java 应用程序,如何优雅地启停.管理程序的生命 ...
- Shell - [01] 概述
一.shell是什么 Shell 是一个命令解释器,接收应用程序/用户命令去调用操作系统内核. Shell 是一个功能强大的编程语言,易编写.易调试.灵活性强. 二.shell的解析器有哪些 [roo ...
- Ansible - [11] Roles
前言 Q1:什么是Roles 在实际生产环境中,会编写大量的playbook文件来实现不同的功能.而且,每个playbook还可能会调用其他文件(变量文件),对于海量的.无规律的文件,管理是个问题.A ...
- 关于我在使用Steamlit中碰到的问题及解决方案总结
Steamlit 并不支持一个可以预览本地文件的路径选择器(并不上传文件) 解决方案:使用 Python 自带的 tkinter 来完成 参考:[Streamlit 选择文件夹的曲折方案]Stream ...
- 【论文随笔】基于会话的推荐系统构建方法调查(Survey On Methods For Building Session-Based Recommender Systems)
前言 今天读的论文为一篇于2023年发表在国际开放信息技术杂志(International Journal of Open Information Technologies)的论文,文章是关于构建基于 ...
- PHP 命名空间与spl_autoload_register() 自动加载机制
转:https://www.cnblogs.com/chihuobao/p/9895202.html include 和 require 是PHP中引入文件的两个基本方法.在小规模开发中直接使用 in ...