之前在某个地方看到的一个问题,“如果为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虚拟机分配堆内存大于机器物理内存会怎么样?的更多相关文章

  1. java虚拟机的堆内存配置

    官网文档地址:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html 接录如下: -XX:MaxHeapSize=si ...

  2. Java虚拟机:JVM内存分代策略

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代.老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存 ...

  3. java虚拟机学习-JVM内存管理:深入垃圾收集器与内存分配策略(4)

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 概述: 说起垃圾收集(Garbage Collection,下文简称GC),大部分人都把这项 ...

  4. 《深入理解 java 虚拟机》学习 -- 内存分配

    <深入理解 java 虚拟机>学习 -- 内存分配 1. Minor GC 和 Full GC 区别 概念: 新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java ...

  5. Java虚拟机学习 - 体系结构 内存模型

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”, 它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB,最 ...

  6. Java虚拟机学习 - 体系结构 内存模型(1)

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代" ."非堆",  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内 ...

  7. java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3)

    概述 Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来. 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又 ...

  8. Java虚拟机学习 - 体系结构 内存模型(转载)

    一:Java技术体系模块图 二:JVM内存区域模型 1.方法区 也称"永久代” .“非堆”,  它用于存储虚拟机加载的类信息.常量.静态变量.是各个线程共享的内存区域.默认最小值为16MB, ...

  9. 深入理解Java虚拟机(自动内存管理机制)

    文章首发于公众号:BaronTalk 书籍真的是常读常新,古人说「书读百遍其义自见」还是很有道理的.周志明老师的这本<深入理解 Java 虚拟机>我细读了不下三遍,每一次阅读都有新的收获, ...

  10. 实战Java虚拟机之一“堆溢出处理”

    从今天开始,我会发5个关于java虚拟机的小系列: 实战Java虚拟机之一“堆溢出处理” 实战Java虚拟机之二“虚拟机的工作模式” 实战Java虚拟机之三“G1的新生代GC” 实战Java虚拟机之四 ...

随机推荐

  1. [SHOI2017] 摧毁“树状图”

    首先只要得到 \(x=0\) 时的答案,就可以 \(AC\) 本题.这是很重要的. 考虑由于不能有重复经过的边,所以两路径交点数量 \(\le 1\). 容易想到设 \(dp_u\) 表示以 \(u\ ...

  2. JNZ项目犯错记

    年度绩效因为JNZ项目没有达到绩效目标被打了低绩效,这个是我从业这么多年来第一次低绩效,记一下我在这个项目中犯的错误. 项目起源 公司业务是一个ToB的电商场景,已经经营数年了,公司会给大客户一定账期 ...

  3. 启动U盘制作-小白保姆式超详细刷机教程

    疑难解答加微信机器人,给它发:进群,会拉你进入八米交流群 机器人微信号:bamibot 简洁版教程访问:https://bbs.8miyun.cn 一.准备工作 需要用到的工具: 1.一台Window ...

  4. 一个简单的PHP单文件路由类示例

    <?php class Router { protected $routes = array(); protected $basePath; public function __construc ...

  5. Abaqus Matrix Genrate 分析 | 输出总体刚度

    引言 abaqus 可以输出模型的刚度/质量/阻尼/载荷矩阵等: 输出单元刚度矩阵 输出范围可以是一个单元,也可以是多个单元 输出总体刚度矩阵 输出的数据是整个模型的刚度矩阵,或者是某一特定区域的总体 ...

  6. Win系统重装备忘

    蒙德,致态的盘坏块激增,似乎损坏到了系统文件:屏幕截屏会卡,关机后直接该块硬盘内的文件内容回滚,出现驱动报错要求重启... 然后尝试了DiskGenuis迁移系统,PE模式不能用,热迁移后似乎正常分区 ...

  7. Manus,没有秘密「注解版」

    近来Manus走红,「争论」不断,我也在前文<Manus爆火,是硬核还是营销?>中阐述过自Manus发布后,行业讨论以及开源复刻的信息,以及我们如何结合蚂蚁图计算(TuGraph)技术,实 ...

  8. windows goland go exec "gcc": executable file not found in %PATH%

    问题 windows 本地缺少 gcc 编译器 解决方案 下载安装使用 MinGW-w64 第一种 https://winlibs.com/#download-release 下载后解压到磁盘中,然后 ...

  9. centos7 docker卸载老版本并升级到最新稳定版本

    一.前言 docker的版本分为社区版docker-ce和企业版dokcer-ee社,区版是免费提供给个人开发者和小型团体使用的,企业版会提供额外的收费服务,比如经过官方测试认证过的基础设施.容器.插 ...

  10. 开源!Django-Vue3-Admin的Python后台管理系统

    Django-Vue3-Admin 项目简介 Django-Vue3-Admin 是一个基于 Django + Vue3 的前后端分离的后台管理系统,采用了最新的前后端技术栈,内置了丰富的功能模块,可 ...