之前面试的时候有一个比较有意思的问题: 在操作系统层面上,申请超过物理内存的虚拟内存,会发生什么。
比如说,是4G内存的机器,申请12G的虚拟内存会发生什么?
当时不太清楚相关方面的知识,于是后面就来仔细了解一下。
虚拟内存
可以先来回顾一下虚拟内存。
以前的机器,或者单片机,都说直接操作物理地址的。
这意味着,不同应用程序之间的不存在内存隔离,可以直接访问物理内存,如果第一个程序在 0x123456789 的位置写入一个新的值,可能会擦掉第二个程序存放在相同位置上的内存。
所以,需要把进程所使用的地址 隔离 开来,即让操作系统为每个进程分配独立的一套 虚拟地址 ,人人都有,互不干涉。
虚拟内存为每个进程提供了一个一致的、私有的地址空间,它让每个进程产生了一种自己在独享主存的错觉(每个进程拥有一片连续完整的内存空间)。
所以,我们在程序所使用的内存地址叫实际上叫做 虚拟内存地址
虚拟地址会通过 CPU 芯片中的内存管理单元(MMU)的映射关系,来转换变成物理地址,然后再通过物理地址访问内存
所以,我们到底可以申请多少的虚拟内存呢?是不是说虚拟内存和物理内存是一一对应的呢?
操作系统虚拟内存
我们需要先来了解不同位的操作系统的虚拟内存大小。
32 位操作系统和 64 位操作系统的虚拟地址空间大小是不同的,
在 Linux 操作系统中,虚拟地址空间的内部又被分为 内核空间 和 用户空间两部分
用户空间: 大约是3GB,用于存放用户级应用程序的代码和数据。这是用户应用程序可以访问的部分地址空间。
内核空间: 剩余的约1GB,用于操作系统内核的代码和数据。只有操作系统内核可以访问内核空间,而用户应用程序不能直接访问它。
所以说,我们最大能申请的虚拟内存的大小需要根据操作系统位来判断。
mac 可以用 uname -m
查看
"x86_64",则表示系统是 64 位的,如果看到 "i386",则表示系统是 32 位的。
32 位系统:最大能申请3G的虚拟内存
64位系统: 最大能申请128T的虚拟内存
所以,我们了解了第一个影响虚拟内存大小的点:操作系统的位数
现在我们可以回答了其中一种情况:
在 32 位操作系统、4GB 物理内存的机器上,申请 12GB 内存,会怎么样?
因为 32 位操作系统,进程最多只能申请 3 GB 大小的虚拟内存空间,所以进程申请 12GB 内存的话,在申请虚拟内存阶段就会失败。
我们往下
操作系统访问虚拟内存过程
我们用c语言分配内存的时候,都会用到malloc 函数。
应用程序通过 malloc 函数申请内存的时候,实际上申请的是虚拟内存,此时并不会分配物理内存。
当应用程序读写了这块虚拟内存,
- CPU 就会去访问这个虚拟内存,
- 如果发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的处理。
- 缺页中断处理函数会看是否有空闲的物理内存:
3.1 . 如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。
3.2 如果没有空闲的物理内存,那么内核就会开始进行回收内存 的工作,如果回收内存工作结束后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM (Out of Memory)。
我们可以看到,如果访问了虚拟内存,就会开始分配物理内存。
那么假如我们分配了虚拟内存,不访问呢?
没错,只要不读写这个虚拟内存,操作系统就不会分配物理内存。
所以我们可以明白: 在 64 位操作系统、4GB 物理内存的机器上,申请 12G 内存,会怎么样?
64 位操作系统,进程可以使用 128 TB 大小的虚拟内存空间,所以进程申请 12GB 内存是没问题的,因为进程申请内存是申请虚拟内存,并没有读写。
尝试
下面尝试用c语言分配虚拟内存,使用malloc函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MEM_SIZE 1024 * 1024 * 1024
int main() {
char* addr[4];
int i = 0;
for(i = 0; i < 0; ++i) {
addr[i] = (char*) malloc(MEM_SIZE);
if(!addr[i]) {
printf("执行 malloc 失败, 错误:%s\n",strerror(errno));
return -1;
}
printf("主线程调用malloc后,申请1gb大小内存,此内存起始地址:0X%p\n", addr[i]);
}
//输入任意字符后,才结束
getchar();
return 0;
}
我一开始先不分配内存, 并且运行上面的c文件
(只用看下面那条)
可以看到,即使不主动malloc分配虚拟内存,也会有32GB的虚拟内存。
接下来malloc 12G
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define MEM_SIZE 1024 * 1024 * 1024
int main() {
char* addr[4];
int i = 0;
for(i = 0; i < 12; ++i) {
addr[i] = (char*) malloc(MEM_SIZE);
if(!addr[i]) {
printf("执行 malloc 失败, 错误:%s\n",strerror(errno));
return -1;
}
printf("主线程调用malloc后,申请1gb大小内存,此内存起始地址:0X%p\n", addr[i]);
}
//输入任意字符后,才结束
getchar();
return 0;
}
可以看到 多分配了12G虚拟内存
Swap 机制
先总结一下上面的知识,
- 在 32 位操作系统,因为进程最大只能申请 3 GB 大小的虚拟内存,所以直接申请 8G 内存,会申请失败。
- 在 64 位操作系统,因为进程最大只能申请 128 TB 大小的虚拟内存,即使物理内存只有 4GB,申请 12G 内存也是没问题,因为申请的内存是虚拟内存。
程序申请的虚拟内存,如果没有被使用,它是不会占用物理空间的。当访问这块虚拟内存后,操作系统才会进行物理内存分配。
如果申请物理内存大小超过了空闲物理内存大小呢?
其实在 OOM 前还有还有一个检查: 是否开启 Swap 机制
什么是 Swap 机制?
当内存使用存在压力的时候,会开始触发内存回收行为,会把这些不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存就可以了。
这种,将内存数据换出磁盘,又从磁盘中恢复数据到内存的过程,就是 Swap 机制负责的。
所以:
- 如果没有开启 Swap 机制,程序就会直接 OOM;
- 如果有开启 Swap 机制,程序可以正常运行。
总结:
分配虚拟内存空间,考虑三个条件:
- 操作系统是 32 位的,还是 64 位的
- 申请完 8G 内存后会不会被使用?
- 操作系统有没有使用 Swap 机制?
参考
https://sylvanassun.github.io/2017/10/29/2017-10-29-virtual_m...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。