1. 内存映射
虚拟内存:
Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的。进程可以很方便地访问虚拟内存。
虚拟地址空间分为:内核空间和用户空间。不同字长处理器(32位/64位)地址空间范围也不同。进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。
所有进程的虚拟内存加起来要比实际内存大得多,因此不是所有虚拟内存都分配内存,只有实际使用的虚拟内存才会分配物理内存,分配后的物理内存通过内存映射进行管理。内存映射,其实就是将虚拟内存地址映射到物理内存地址。为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址与物理地址的映射关系。当进程访问的虚拟地址在页表中查不到时,系统会产生一个缺页异常,进入内核空间分配物理内存、更新进程页表,最后再返回用户空间,恢复进程的运行。
内存映射的最小单位为页,通常是4KB大小。4G内存需4G/4K=100多万页表项才能完成整个地址空间映射。为解决页表项过多的问题,Linux提供两种机制:多级页表和大页 (HugePage):
- 多级页表:映射关系改为区块索引和区块内的偏移。Linux采用的是四级页表管理内存页,前四个页表项用于选择页,最后一个索引表示页内偏移。
- 大页:更大的内存块,通常大小有2MB和1GB,通常应用在使用大量内存的Linux进程中。
2. 虚拟内存空间分布
内核空间和用户空间,其中用户空间包括:
只读段,包括代码和常量等。
数据段,包括全局变量等。
堆,包括动态分配的内存,从低地址开始向上增长。
文件映射段,包括动态库、共享内存等,从高地址开始向下增长。
栈,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。
堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。
3. 内存分配与回收
malloc() 是 C 标准库提供的内存分配函数,当进程通过 malloc() 申请内存后,内存并不会立即分配,而是在首次访问时,才通过缺页异常陷入内核中分配内存。
malloc() 对应到系统调用有两种实现方式: brk() 和 mmap()。
- 小块内存(小于 128K),C 标准库使用 brk() 来分配,内存释放并不会立刻归还系统,而是被缓存起来重复使用。减少缺页异常的发生,提高内存访问效率,但频繁的内存分配和释放会造成内存碎片。
- 大块内存(大于 128K),使用mmap() 来分配,在文件映射段找一块空闲内存分配出去。频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大
在应用程序用完内存后,还需要调用 free() 或 unmap() ,来释放这些不用的内存。
当有大量比页还小的的对象要分配时,为它们分配单独的页会浪费内存。因此,在用户空间,malloc() 通过 brk() 分配的内存,在释放时并不立即归还系统,而是缓存起来重复利用。在内核空间,Linux 则通过 slab 分配器来管理小内存,slab 主要作用就是分配并释放内核中的小对象。
4. 处理内存紧张
回收缓存,比如LRU回收最近使用最少的内存页面。
回收不常访问的内存,把不常用内存通过交换分区写入磁盘。
- Swap 把系统的可用内存变大了。不过通常只在内存不足时才会发生 Swap 交换。由于磁盘读写的速度远比内存慢,Swap 会导致严重的内存性能问题。
杀死进程,系统通过OOM直接杀掉占用大量内存的进程。
OOM是内核的一种保护机制,监控进程的内存使用,使用 oom_score 为每个进程的内存使用情况进行评分:进程消耗内存越大,oom_score 就越大;进程运行占用 CPU 越多,oom_score 就越小。
oom_adj 的范围是 [-17, 15],数值越大,表示进程越容易被 OOM 杀死;数值越小,表示进程越不容易被 OOM 杀死,其中 -17 表示禁止 OOM。
echo -16 > /proc/$(pidof sshd)/oom_adj
以这种方式可以调整某进程的oom_adj。
5. 内存使用查看
查看系统内存使用
使用free
命令时的各列含义:
total 总内存大小;
used 已使用内存的大小,包含了共享内存;
free 未使用内存的大小;
shared 共享内存的大小;
buff/cache 缓存和缓冲区的大小;
available 新进程可用内存的大小。available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。
查看进程内存使用
使用top
时的内存相关列的含义:
- VIRT 进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
- RES 常驻内存的大小,也就是进程实际使用的物理内存大小。
- SHR是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态链接库,也都算在 SHR 里。当然,SHR 也包括了进程间真正共享的内存。
- %MEM 是进程使用物理内存占系统总内存的百分比。