06-内存
内存是怎么工作的
内存映射
我们平常说的内存其实指的是物理内存,而操作系统中给每个进程分配的是虚拟空间,相当于告诉每个进程它完整的拥有整个物理内存。虚拟内存又分为用户空间和内核空间。
每个虚拟空间的理论范围与CPU的字长(单个CPU指令一次处理数据的最大长度)有关。如64位系统是从全0到全1的地址,用户空间和内核空间的虚拟内存大小都是128T。
而虚拟内存到物理内存的内存映射由CPU的内存管理单元MMU管理,MMU以页为单位管理内存,每个页4KB。如果每个地址都有一个页表项,那么页表项会非常巨大。针对这个一般有多级页表和大页两种优化办法。
LInux用的就是四级页表的方法。CPU拿到一个虚拟地址,比如 0x0000 7FFF 1234 5678,它的任务就是像上面查黄页一样,一步步把它拆开。在常见的64位系统(使用48位虚拟地址)中,这个地址会被精确地拆成5部分:
PGD索引
[47:39] (高9位)
在“区县手册(PGD)”里找对应条目
“海淀区”
PUD索引
[38:30] (接下来9位)
在“区级分册(PUD)”里找对应条目
“中关村大街”
PMD索引
[29:21] (接下来9位)
在“街道分册(PMD)”里找对应条目
“XX大厦A座”
PTE索引
[20:12] (接下来9位)
在“最终住户名录(PTE)”里找对应条目
“1001室”
页内偏移
[11:0] (低12位)
在找到的“房间(物理页)”里的具体位置
电话机在房间里的具体位置
因为每级索引都是9位,所以每本“分册”(每个页表)正好有 2⁹ = 512 个条目。每个条目大小是8字节,因此一个4KB的物理页刚好能装下整个页表。
大表一般是2M或1GB大小。
内存回收
回收缓存,使用LRU算法回收最近不常使用内存
可以通过
meminfo查看维护的活跃链表和不活跃链表,越靠近链表尾部越代表不常使用active:记录活跃的内存页
inactive:记录不活跃的内存页
又分为文件页和匿名页,对应着缓存回收和swap回收
回收不常访问内存,将这部分内通过交换分区写到磁盘
OOM,通过杀死进程释放大量内存
oom是基于虚拟内存的,一旦申请的虚拟内存加上机器上已经使用的内存大于物理内存就会触发OOM
其中SWAP(交换分区)一般只在内存非常紧张的时候才会使用,而且磁盘的读写性能比内存慢非常多,所以使用swap会导致非常严重的性能问题。
OOM通过计算得分,将得分高的进程杀死,计分规则简单来说可以为:
内存使用高的进程得分高
CPU使用高的进程得分低
可以通过调整oom_adj的值来限制得分行为,范围为[-17,15],值越高越容易被杀死,-17禁止OOM行为
Buf/Cache
Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。
Cached 是从磁盘读取/写文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
SReclaimable 是 Slab 的一部分。Slab包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。
swap
内存紧张时将直接进行内存回收,Linux中还有一个专门的内核进程定期回收内存,kswap0定义了三个内存阈值(watermark,也称水位)。
分别是页高阈值pages high/页第阈值pages low/页最小阈值pages min,剩余内存则用pages free表示。
当pages_free低于pages_min,说明可用内存都耗尽了,这时候只有内核才能分配内存
当pages_free处于pages_low和pages_min之间,说明内存紧张,触发内存回收,直到内存大于高阈值
当pages_free处于pages_low和pages_high之间,说明内存有一定压力,但还可以满足新内存请求
当pages_free处于pages_high之上,说明内存非常充足
NUMA
系统剩余内存充足时Swap使用率却升高——确实是Linux内存管理中的一个经典问题。这通常与服务器的NUMA(非统一内存访问)架构以及Linux内核与之配合的内存回收策略密切相关。
内存访问模式
所有CPU核心通过同一总线访问内存,延迟统一
每个CPU节点有本地内存,访问其他节点的内存为远程访问,延迟更高
内存管理视角
系统内存是一个统一的整体池
系统内存由多个节点(Node)的本地内存池组成
默认分配策略
申请内存时从全局内存池分配
优先从发出请求的CPU所在节点的本地内存池分配
导致Swap的场景
通常只在全局内存不足时发生
某个NUMA节点的本地内存不足时,即使其他节点有空闲内存,系统也可能为满足“本地分配”偏好而回收该节点内存(包括Swap),而非从远程节点分配
NUMA的“本地偏好”与瓶颈
在NUMA架构中,每个CPU节点访问自己的本地内存非常快,而访问其他节点的内存(远程访问)则速度较慢。因此,Linux内核的默认内存分配策略带有强烈的 “本地偏好” :当一个进程在某个CPU节点上运行时,内核会优先从该节点的本地内存中为其分配内存。这样,当某个进程(比如MySQL)在节点0上运行并大量申请内存时,即使节点1有大量空闲内存,内核仍会倾向于清空节点0的缓存、甚至Swap出节点0的匿名页,以努力满足“本地分配”。这便导致了“全局内存充足,但局部节点内存紧张而触发Swap”的现象。
内存回收的水位线机制
内核守护进程
kswapd负责在每个NUMA节点内定期回收内存。它为该节点定义了三条关键的水位线(Watermark),这些水位线的查看方式如下:
pages处的min、low、high,就是上面提到的三个内存阈值,而free是剩余内存页数,它跟后面的 nr_free_pages 相同
nr_zone_active_anon和nr_zone_inactive_anon,分别是活跃和非活跃的匿名页数
nr_zone_active_file和nr_zone_inactive_file,分别是活跃和非活跃的文件页数。
某个 Node 内存不足时,系统可以从其他 Node 寻找空闲内存,也可以从本地内存中回收内存。具体选哪种模式,你可以通过 /proc/sys/vm/zone_reclaim_mode 来调整。它支持以下几个选项:
默认的 0 ,也就是刚刚提到的模式,表示既可以从其他 Node 寻找空闲内存,也可以从本地回收内存
1、2、4 都表示只回收本地内存,2 表示可以回写脏数据回收内存,4 表示可以用 Swap 方式回收内存
当某个节点的**空闲内存(pages_free)低于低水位线(low)** 时,kswapd会被唤醒,开始在该节点内回收内存,以期将空闲内存提升到高水位线(high)以上。回收的对象包括两类:
文件页:缓存的文件数据等。回收代价低(干净页可直接丢弃,脏页需写回磁盘)。
匿名页:应用程序动态分配的堆内存等。回收需要通过Swap机制写入磁盘。
Swappiness:回收倾向的调节器
内核参数
/proc/sys/vm/swappiness(默认值通常为60)决定了内核在回收内存时更偏好回收匿名页(触发Swap)还是文件页。值越大(最大100):越积极使用Swap,即更倾向于回收匿名页。
值越小(最小0):越消极使用Swap,即更倾向于回收文件页(缓存)。
结合NUMA架构,即便
swappiness设置为0,当某个节点的文件页本身也非常少(即没有多少缓存可以释放)时,内核为了满足新的本地内存分配请求,仍然会触发Swap
可以使用 numactl --hardware 来查看每个Node的使用情况
这个界面显示,我的系统中只有一个 Node,也就是Node 0 ,而且编号为 0 和 1 的两个 CPU, 都位于 Node 0 上。另外,Node 0 的内存大小为 7977 MB,剩余内存为 4416 MB。
可以查看 /proc/{pid}/status 文件中的VmSwap 字段查看进程Swap换出的虚拟内存大小
![[Pasted image 20260106222431.png]]
最后更新于