一、内存映射 **
1. 通常所说的内存容量,指的是物理内存。进程是不能直接访问物理内存的。
Linux 内核给每个进程都提供一个独立的虚拟地址空间,这个地址空间是连续的。(也就是虚拟内存)
2. 虚拟地址空间的内部又被分为内核空间和用户空间。
32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间。
进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。
3. 内存映射,其实就是将虚拟内存地址映射到物理内存地址。
(1) 页表实际上存储在 CPU 的内存管理单元 MMU 中。
(2) 不是所有的虚拟内存都会分配物理内存,当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常。
(3) 缺页异常时,需要进入内核空间分配物理内存,更新页表,再返回用户空间。
(4) MMU规定了一个内存映射的最小单位,4K。 所以4K的数据可以集中在一个页面中,可以访问加速。
二、虚拟内存空间分布 **
1. 用户空间内存,其实又被分成了多个不同的段
(1) 只读段,包括代码和常量等。
(2) 数据段,包括全局变量等。
(3) 堆,包括动态分配的内存。
(4) 文件映射段,包括动态库、共享内存等。
(5) 栈,包括局部变量和函数调用的上下文等。
三、内存分配与回收 *
1. malloc() 是 C 标准库提供的内存分配函数,有两种实现方式,即 brk() 和 mmap()。
(1) brk(): 小块内存(小于 128K)时使用。
在堆段位置, 释放后并不会立刻归还系统,而是被缓存起来,可以重复使用,提高内存访问效率。
(2) mmap():大块内存(大于 128K)时使用。
在文件映射段位置,找一块空闲内存分配出去。
2. 系统发现内存紧张时,会通过三种方式来回收内存:
(1) 回收缓存 LRU
使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面.
(2) 回收不常访问的内存 SWAP
把不常用的内存通过交换分区(Swap)直接写到磁盘中,当进程访问这些内存时,再从磁盘读取这些数据到内存中.
(3) 杀死进程 OOM
通过 OOM(Out of Memory),直接杀掉占用大量内存的进程。
OOM使用 oom_score 为每个进程的内存使用情况进行评分:
一个进程消耗的内存越大,oom_score 就越大;
一个进程运行占用的 CPU 越多,oom_score 就越小...
oom_score 越大,越容易被 OOM 杀死。值范围[-17,15]
防止被OOM杀死: echo -16 > /proc/$(pidof sshd)/oom_adj
四、如何查看内存使用情况 **
1. free命令: (有些系统需要 free -a )
total :总内存大小;
used :已使用内存的大小,包含了共享内存;
free :未使用内存的大小;
shared : 共享内存的大小;
buff/cache : 是缓存和缓冲区的大小;
available :剩余可用内存的大小。
不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。
2. top命令: (查看具体的进程的使用内存状况)
VIRT : 虚拟内存的大小。
RES : 常驻内存的大小。 实际使用的物理内存大小,但不包括 Swap 和共享内存。
SHR : 共享内存的大小。比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
%MEM:进程使用物理内存占系统总内存的百分比。
(1) 虚拟内存并不会全部分配物理内存,虚拟内存都比常驻内存大得多。
(2) 共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库。所以计算时不能直接相加。
五、其他一些补充 **
1. 程序启动后,分配的虚拟内存大小不是固定的,堆段和内存映射段遇到预分配空间不足情况时,是会动态扩展的。
2. 虚拟内存地址,段内大致连续,段间不连续。各段之间存在地址间隙或保护页,整体虚拟地址空间不连续。 文件映射段段内也不连续,会动态增加映射区域。
3. 程序启动时分配的虚拟内存 = 静态段(代码/数据/BSS) + 堆预留空间 + 栈 + 动态库映射 + 对齐间隙。
4. 实际物理内存 远小于虚拟内存,不立刻分配,到使用时先触发缺页异常,然后才分配。