在Linux系统中,每个进程都独有一个虚拟地址空间(Virtual Address Space),由内核维护内存映射。为完成内存映射(虚拟内存地址映射到物理内存地址),内核为每一个进程维护一张页表。而页表存储在CPU的内存管理单元MMU中(即通过硬件完成了内存地址的查找)。
缺页异常
当进程访问的虚拟地址在页表中找不到时,就会产生缺页异常。此时会陷入内核空间完成物理内存分配、更新进程页表,然后恢复进程运行。
MMU以页为单位来管理内存,每个页通常为4KB大小,所以每一次内存映射需要关联4KB的整数倍的内存空间。
为了解决页表项过大问题,Linux提供多级页表(实际上是四级页表)和大页(HugePage)机制。
Linux虚拟地址空间分布
Linux的虚拟地址空间,大大增加了进程的寻址空间,32位系统的虚拟地址空间分布如图:
- 只读段:只读,不可写;包括代码段、常量
- 读/写段(或数据段):保存全局变量、动态变量
- 堆:即平时说的动态内存,自下往上增长;堆内存由应用程序自己分配管理,依靠应用程序进行释放,但内核可将不常用数据换出到swap。大于128K的内存通过malloc()函数来分配(使用unmap()来释放),堆顶位置小于128K的内存通过brk()来分配(使用free()来释放)。
- 文件映射区域:动态库、共享内存等映射物理空间的内存,自上往下增长;一般为mmap()函数所分配的虚拟地址空间
- 栈:用于维护函数调用的上下文空间,一般为8M,
ulimit -s
查看 - 内核虚拟空间:内核管理,对用户代码不可见。内核空间与用户空间的边界由TASK_SIZE控制。
对于64为Linux,内核空间占据顶部128T的虚拟地址空间,用户空间占据底部128T的虚拟地址空间。中间部分是未定义的。
另一张Linux虚拟地址空间分布图:
Linux内存回收
通过Linux虚拟地址空间分布,我们可以看出,Linux内存回收主要针对堆和文件映射区域。
- 文件页:cache、buffer,通过内存映射的文件映射页
- 匿名页:堆内存
- 目录缓存和inode缓存
Linux内存回收时机
Linux提供2种内存回收时机:
- 直接内存回收
- 内核线程回收
直接内存回收
当发生内存分配,而此时的剩余空闲内存不足时,会执行立即内存回收。
内核线程回收
内核线程kswapd
会定期扫描内存使用量,当剩余内存小于pages_free_low
的值时,会进行内存回收,并确保可用内存高于pages_free_high
。可通过修改/proc/sys/vm/min_free_kbytes
文件的值来修改pages_free_min
的值,另外两个值会自动计算得出。
NUMA架构中,每一个NUMA节点都会有一个kswapd线程。可通过numactl --hardware
命令查看NUMA节点信息。
# cat /proc/sys/vm/min_free_kbytes
90112
# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29
node 0 size: 32642 MB
node 0 free: 188 MB
node 1 cpus: 10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39
node 1 size: 32768 MB
node 1 free: 68 MB
node distances:
node 0 1
0: 10 21
1: 21 10
Linux内存回收手段
Linux通过以下方式进行内存回收:
- 通过LRU算法回收文件页缓存:包括cache、buffer&#x