Linux内核页面回收与OOM Killer深度实战:从LRU链表熔断到cgroup内存爆炸

引言

在Linux内核的庞大体系中,内存管理子系统是最复杂、最核心的子系统之一。如果说SLUB分配器解决了"如何高效分配小对象"的问题,那么页面回收(Page Reclaim)机制则回答了一个同样关键的命题:当物理内存不足时,内核如何将哪驱逐出境?

页面回收是Linux内存管理的"刹车系统"。当一个进程请求分配内存(无论是用户态的malloc还是内核态的kmalloc),而系统当前没有足够的空闲页面时,页面回收机制就会被触发,通过将不活跃的页面写入交换分区(swap)或回写脏页到磁盘,来腾出物理内存空间。这个过程如果处理不当,会导致系统卡顿、性能急剧下降,甚至触发OOM(Out Of Memory) Killer随机杀死进程。

本文将从LRU链表的底层数据结构入手,逐步深入页面回收的完整路径——从kswapd后台守护进程的异步回收,到进程因内存不足陷入的直接回收(direct reclaim),再到最后的终极大招OOM Killer。同时会覆盖cgroup v1/v2的内存限制机制、swapiness调优、以及实际生产环境中遇到的OOM实战案例分析。

一、LRU链表:页面回收的数据基础

1.1 为什么需要LRU?

操作系统面临的核心矛盾是:物理内存远小于进程的虚拟地址空间需求。为了让更多进程"感觉"自己拥有充足内存,内核必须将暂时不用的页面换出到磁盘。但哪些页面是"暂时不用"的呢?这就需要一种机制来追踪页面的访问频率——LRU(Least Recently Used,最近最少使用)算法应运而生。

理想LRU需要为每个页面维护一个精确的时间戳并按时间排序,但这在工程上不现实(需要在每次内存访问时更新数据结构)。Linux采用了一种近似LRU方案——双链表(Active/Inactive)算法,通过两个链表和一个访问位(Referenced/Referenced bit)来实现近似最近最少使用的判断。

1.2 五种LRU链表类型

从内核2.6.28开始,Linux维护五种独立的LRU链表:

LRU链表类型说明可回收性
LRU_INACTIVE_ANON匿名页非活跃匿名页(进程堆/栈)需写入swap
LRU_ACTIVE_ANON活跃匿名页活跃匿名页需先demote
LRU_INACTIVE_FILE文件缓存非活跃文件页(page cache)直接丢弃
LRU_ACTIVE_FILE文件缓存活跃文件页需先demote
LRU_UNEVICTABLE不可驱逐mlocked或Bolted页面永远不回收

匿名页(Anonymous Page):没有后备存储的页面,如进程的堆内存、栈空间。这类页面一旦被释放就无法恢复(除非重新计算),所以若要回收必须写入swap分区。

文件页(File-backed Page):有后备文件映射的页面(如通过mmap映射的文件、page cache)。干净的页面可以直接丢弃(下次从文件读回即可),脏页需要先回写到磁盘。因此文件页的回收代价通常远低于匿名页。

SHMEM(共享内存/tmpfs)是一个特殊情况——它看起来像文件页,但被放在LRU_ANON链表中,因为它也没有真正的磁盘后备存储(除非tmpfs挂载在swap上)。

1.3 zone_reclaim_mode与NUMA感知

在NUMA系统中,还需要考虑页面所在的NUMA节点。每个NUMA节点维护独立的LRU链表(通过pglist_data→lruvec)。zone_reclaim_mode(/proc/sys/vm/zone_reclaim_mode)控制是否在回收时优先回收本节点页面:

  • 0:不启用zone reclaim,从所有节点回收
  • 1:优先从本节点回收(适合内存绑定型应用如数据库)
  • 2:只在本地节点耗尽时才fallback
  • 4:结合swap的zone reclaim

对于MySQL、PostgreSQL等NUMA敏感型数据库,通常建议设置zone_reclaim_mode=1,避免跨NUMA节点访问内存带来的延迟惩罚。

1.4 页面如何在Active/Inactive之间移动

参考位(PG_referenced flag)是页面在双链表间移动的判断依据:

晋升(Promotion):Inactive页被访问时,referenced bit被置位。在第二轮扫描时如果referenced仍为1,该页被提升到Active链表。这意味着一个页面需要被连续访问两次才能进入Active链表。

降级(Demotion):Active页如果经过一轮扫描后referenced bit为0(即没有被访问),则被降级到Inactive链表。Inactive链表中的页面是回收的首要目标。

这两个操作构成了LRU的"时钟算法"——时钟指针持续扫描页面,根据访问情况决定页面的去留。在 Linux 5.15+ 中,多 Gen LRU(mgLRU)进一步优化了这个机制,引入了代龄(generation)概念来更精准地区分热点页面。

二、页面回收机制:kswapd与Direct Reclaim

2.1 水位线(Watermark)体系

页面回收的触发由三条水位线控制,它们定义在每个NUMA节点的pglist_data中:

enum zone_watermarks {
    WMARK_MIN,       // 最低水位,直接回收的触发点
    WMARK_LOW,       // 低水位,kswapd唤醒阈值
    WMARK_HIGH       // 高水位,kswapd睡眠阈值
};

WMARK_MIN:危险线。当空闲页面低于此值时,分配者必须自己执行直接回收(direct reclaim),即分配操作将阻塞直到回收足够页面。每次kswapd唤醒时会留有少量"min"级空闲页,但它不是给普通分配的。

WMARK_LOW:警戒线。kswapd守护进程被唤醒的阈值。kswapd会持续回收页面直到空闲页面达到WMARK_HIGH。从WMARK_LOW到WMARK_HIGH的差值即为kswapd一次回收的目标量。

WMARK_HIGH:安全线。kswapd停止回收并进入睡眠的阈值。kswapd的目标是将空闲页面维持在WMARK_HIGH以上。

水位线的计算公式(简化版):

WMARK_MIN  = managed_pages * min_percent / 100
WMARK_LOW  = WMARK_MIN + (WMARK_MIN / 4)    // 约高25%
WMARK_HIGH = WMARK_LOW + (WMARK_MIN / 4)    // 约高50%

其中min_percent可通过/proc/sys/vm/min_free_kbytes直接指定WMARK_MIN(单位为KB)。生产服务器通常建议min_free_kbytes=65536~262144(64MB~256MB),用于应对突发内存分配需求。

2.2 kswapd:后台异步守护进程

kswapd是每个NUMA节点上的后台守护进程(kswapd0、kswapd1...),负责在内存水位低于WMARK_LOW时异步回收页面。它的核心循环可以简化为:

// mm/vmscan.c: kswapd()
for ( ; ; ) {
    // 1. 检查是否需要回收(某个zone的free < LOW watermark)
    if (all_zones_ok())
        kswapd_try_to_sleep();    // 睡眠等待唤醒
    
    // 2. 平衡各zone的压力
    balance_pgdat(pgdat, order, classzone_idx);
    
    // 3. 回收直到满足HIGH水位
}

kswapd的回收流程:

  1. 对每个zone,计算回收目标(基于当前水位和请求order)
  2. 按比例在各LRU链表间分配回收配额(匿名页 vs 文件页比例由swappiness决定)
  3. 调用shrink_lruvec()对每个LRU链表执行扫描和回收
  4. 重复直到达标或没有任何可回收页面

kswapd的回收是异步且不阻塞调用者的,但由于需要在持有zone锁的情况下扫描LRU链表,在高并发场景下会带来一定开销。Linux 6.x引入了per-cgroup LRU维护,进一步降低了锁竞争。

2.3 Direct Reclaim:同步回收的痛苦

当进程的内存分配请求无法满足(空闲页低于WMARK_MIN),且kswapd尚未回收足够页面时,分配者进程必须自己执行页面回收,这就是直接回收(direct reclaim)。

直接回收发生在分配者的上下文中,意味着:

  • 分配者被阻塞:进程在allocate→reclaim之间无法继续执行,增加延迟
  • 可能递归回收:reclaim过程中可能触发新的内存分配(如创建shrinker控制结构),形成递归
  • THP分配更容易触发:分配2MB的大页需要的连续页面多,失败概率更高,更容易触发direct reclaim

direct reclaim的入口函数:

alloc_pages()
  └── __alloc_pages_nodemask()
       └── __alloc_pages_slowpath()    // 慢路径
              └── __alloc_pages_direct_reclaim()   // direct reclaim入口
                     └── try_to_free_pages()        // 尝试释放pages
                            └── do_try_to_free_pages()
                                   └── shrink_zones()
                                          └── shrink_lruvec()

诊断direct reclaim:观察/proc/vmstat中的pgsteal_direct字段增长、vmstat中的si/so字段(swap频率)、或使用blktrace追踪swap I/O。perf sched latency也能看到进程因reclaim导致的调度延迟。

2.4 页面回收的扫描与驱逐决策

shrink_lruvec()是核心回收函数,它决定哪些页面被扫描、哪些被驱逐:

shrink_lruvec()
  ├── shrink_list(LRU_INACTIVE_ANON)  // 首先扫描非活跃匿名页
  ├── shrink_list(LRU_ACTIVE_ANON)    // 非活跃不够时扫描活跃匿名页
  ├── shrink_list(LRU_INACTIVE_FILE)  // 接着扫描非活跃文件页
  ├── shrink_list(LRU_ACTIVE_FILE)    // 最后扫描活跃文件页
  └── shrink_list() 内部:
       ├── isolate_lru_pages()  // 从LRU链表中隔离一批页面
       ├── shrink_page_list()   // 对隔离的页面做驱逐决策
       │    ├── 页面正在被其他路径使用 → 放回LRU(unmap失败)
       │    ├── 脏文件页 → 启动回写(writeback)
       │    ├── 匿名页 → 写入swap
       │    └── 干净未映射页 → 直接释放
       └── putback_lru_page()   // 无法回收的页面放回LRU

页面扫描优先级由swappiness和scanning比例控制:每个LRU链表有一个独立的扫描比例系数。shrink_lruvec()根据目标页数和各链表的优先级,按比例分配扫描量。

三、Swap机制与Swappiness调优

3.1 Swap的本质

Swap(交换空间)是磁盘上的一块区域(分区或文件),用于存放被驱逐的匿名页内容。当进程再次访问被swap-out的页面时,触发page-fault,内核将数据从swap区域读回内存(swap-in)。

Swap的代价极高——磁盘访问延迟大约是内存的10万~100万倍:

存储类型读取延迟吞吐量
DDR4 内存~100ns~50 GB/s
NVMe SSD~10μs~3 GB/s
SAS HDD~5ms~200 MB/s

因此,频繁swap(称为swap thrashing)会直接导致系统瘫痪。Linux的内存管理策略是:尽可能避免swap,只在真正缺页时才会swap-out。但在内存紧张时,将不活跃的匿名页swap-out可以腾出物理页给活跃的文件缓存,反而提升整体性能。

3.2 swappiness参数详解

swappiness(/proc/sys/vm/swappiness, 默认值60)控制swap回收与文件页回收之间的权重比例:

核心逻辑:swappiness不是简单的概率值,而是两个子系统(匿名页回收 vs 文件页回收)扫描量之间的比例系数。它被转换为两个数值:文件页基准值200,匿名页基准值 = 200 + swappiness。扫描量之比为:anon_scan : file_scan = (200 + swappiness) : 200 ≈ (200+swappiness)/200。

换算后的效果:

  • swappiness=0:几乎不swap,优先回收文件页。但注意:如果文件页都已回收完且仍缺水,仍会swap。使用special file-backed内存(如MAP_SHARED)的系统不应设为0。
  • swappiness=1:极其保守的swap策略,仅在使用explicit madvise(MADV_PAGEOUT)时swap
  • swappiness=60(默认):anon_scan : file_scan ≈ 260:200,轻微偏向回收文件页
  • swappiness=100:匿名页和文件页被同等对待
  • swappiness=200(最大值):双倍于文件页扫描匿名页,积极swap

经典调优建议:

  • 数据库服务器(MySQL、Redis):swappiness=1~10,减少swap对延迟的影响
  • 桌面系统:swappiness=60(默认)
  • 批处理/大数据(Hadoop、Spark):swappiness=10~30
  • 使用zram的容器环境:swappiness=150~200

3.3 zram:压缩内存交换

zram(压缩内存块设备)提供了一种替代传统磁盘swap的方案——使用一部分RAM作为交换空间,但其中的数据被实时压缩。优点如下:

  • 避免磁盘I/O延迟(CPU换IO)
  • 压缩比通常为2:1到3:1
  • 适合内存小但CPU强的场景(如嵌入式、树莓派、低配云服务器)

zram配置示例:

# 初始化一个2GB zram设备(压缩后约600MB物理占用)
modprobe zram
echo lz4 > /sys/block/zram0/comp_algorithm    # 选择lz4压缩(速度快)
echo 2G > /sys/block/zram0/disksize              # 设置2GB压缩交换空间
mkswap /dev/zram0
swapon /dev/zram0 -p 10                          # 设置优先级高于磁盘swap

注意zram的优先级(priority参数),更高的优先级意味着内核优先使用zram而非磁盘swap。在容器和云计算环境中,zram已成为标配——Kubernetes默认推荐使用zram替代磁盘swap。

四、OOM Killer:最后的终极大招

4.1 什么情况下触发OOM?

当系统内存极度紧张、direct reclaim无法回收足够页面、kswapd也无法满足需求、且所有页面都是不可回收的(例如全被mlock)时,内核会触发OOM Killer。简而言之:所有页面分配路径都已失败,内核只能杀死进程来释放内存。

OOM Killer的调用路径:

alloc_pages()
  └── out_of_memory()
          └── select_bad_process()    // 选择"最坏"的进程
               └── oom_kill_process() // 杀死选中的进程
                    └── force_sig(SIGKILL)
                    └── memory.oom_group  // 同时杀死cgroup中所有进程

4.2 OOM Score:谁是"最坏"的进程?

OOM Killer通过一个启发式评分系统决定杀死谁。评分存储在/proc/[pid]/oom_score(0~1000),值越高越容易被杀。评分基于以下因素:

基础分(oom_score)= 内存占用百分比 × 1000:占用内存越多,分数越高。具体计算:

// mm/oom_kill.c: oom_badness()
points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) + mm_pages;
// 上述值占总内存的比例乘以1000,得出基础分

调整因子:

  • oom_score_adj(/proc/[pid]/oom_score_adj):管理员可手动调整,范围为-1000到+1000。设为-1000表示禁止OOM Killer杀死该进程(oom_score=0)。设为+1000表示优先杀死。
  • oom_adj(旧版,已废弃):范围-17到+15
  • root进程默认-30分:防止杀死关键系统进程
  • 运行时间短的进程更容易被杀:新进程的oom_score被平方后乘以一个与运行时间相关的因子
  • 交互式/低优先级进程更易被杀:nice值每+1约减少1个oom_score点

实际案例:手动保护关键服务:

# 保护mysqld,将其oom_score_adj设为-1000
echo -1000 > /proc/$(pidof mysqld)/oom_score_adj

# 通过systemd服务配置(推荐)
[Service]
OOMScoreAdjust=-1000

# 让Java进程更容易被杀(常见做法)
echo 700 > /proc/$(pidof java)/oom_score_adj

4.3 OOM Killer日志解读

当OOM Killer触发时,内核会打印详细信息到dmesg(/var/log/syslog),以下是一个典型日志:

Out of memory: Killed process 12345 (java) total-vm:8GB, anon-rss:6GB, file-rss:512MB, shmem-rss:0B, oom_score_adj:0
  ├── 进程名: java
  ├── PID: 12345
  ├── 总虚拟内存: 8GB
  ├── 实际RSS: 6.5GB
  ├── OOM Score: 高 (由内核计算)
  └── 内核会选择score最高的进程杀死

查看历史上的OOM事件:

dmesg | grep -i "out of memory"
journalctl -k | grep -i "oom"

注意:OOM Killer使用SIGKILL(信号9),进程无法捕获或忽略它。进程在被杀前没有任何"优雅关闭"的机会,因此数据库等关键服务必须设置合理的oom_score_adj。

4.4 oom.cgroup:cgroup内存限制触发的独立OOM

在container(容器)环境中,即使系统总体有足够内存,但如果某个cgroup的memory.limit_in_bytes被耗尽,也会触发cgroup级别的OOM。这种OOM仅发生在该cgroup内部,不会杀死其他cgroup的进程。

cgroup OOM event通过memory.oom_control或cgroup.event_control监听:

# 监听cgroup OOM事件
echo 1 > /sys/fs/cgroup/memory/mygroup/memory.oom_control  # 禁止自动杀进程
# 然后通过eventfd监听,当OOM发生时触发通知

生产建议:

  • 为每个容器的memory.limit留出10%~20%的buffer,避免频繁OOM
  • kubernetes的resources.requests.memory应小于resources.limits.memory
  • OOM优先级:cgroup OOM → 全系统OOM(按严重程度递进)

五、cgroup内存限制与回收机制

5.1 cgroup v1 内存子系统

cgroup v1为每个cgroup提供独立的内存使用统计和限制:

文件说明
memory.limit_in_bytes内存硬限制,超出会触发OOM
memory.soft_limit_in_bytes软限制,仅在系统内存紧张时生效
memory.memsw.limit_in_bytesmemory+swap的总限制
memory.kmem.limit_in_bytes内核内存限制(含slab, page tables)
memory.usage_in_bytes当前使用量
memory.oom_control是否禁止OOM Killer(1=禁止)
memory.swappinesscgroup v1.5+独立swappiness

注意事项:cgroup v1的memory controller存在"内核内存不可限制"的问题——kmem accounting默认不开启,开启后又有性能开销。这是v2解决的重要问题之一。

5.2 cgroup v2 内存控制器

cgroup v2重新设计了内存控制器,提供独立的min/max/high/low四级水位:

文件说明
memory.min保证不被回收的最低内存量(硬保护)
memory.low尽力而为的低水位保护
memory.max硬限制,触发cgroup OOM
memory.high软限制,触发throttling(节流)
memory.swap.maxswap使用上限
memory.current当前内存使用量(含文件缓存)
memory.stat详细统计(anon, file, kernel_stack, slab等)

cgroup v2优势:

  • memory.high实现主动节流:当cgroup使用量超过high,内核对其进行回收(throttle),而非等待OOM。配合cpu.max可实现"多用CPU多用内存"的公平调度。
  • 透明内核内存管控:v2的memcg自动统计每个cgroup的内核内存使用(slab、kernel stack等)
  • 内存压力事件通知:通过压力停滞信息(PSI, Pressure Stall Information)接口可提前预警

5.3 PSI: 压力停滞信息(Pressure Stall Information)

PSI(Linux 4.20+)提供了细粒度的内存压力指标,能比水位线更早地预测OOM:

# 查看系统级内存压力
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

# some: 至少一个进程因内存压力停顿
# full: 所有可运行进程同时因内存压力停顿(最严重)

PSI输出解读:

  • avg10/avg60/avg300:过去10秒/60秒/300秒内因内存压力停顿的百分比
  • total:累计停顿时间(μs)
  • some表示"部分内存压力",full表示"系统级内存瓶颈"

PSI与cgroup结合:cgroup v2自动提供per-group的PSI指标:

cat /sys/fs/cgroup/mygroup/memory.pressure
some avg10=5.23 avg60=2.10 avg300=0.85 total=12345
full avg10=0.00 avg60=0.12 avg300=0.05 total=567

配合PSI设置自动告警(avg10 > 20时发送通知),可以在OOM发生前主动干预。

六、实战案例分析

6.1 案例一:Redis内存不足导致频繁direct reclaim

现象:Redis实例响应延迟从0.5ms飙升到50ms,监控显示上下文切换激增。

诊断:vmstat 1显示si/so持续大于0,perf sched latency显示大量时间消耗在shrink_lruvec上。/proc/vmstat中pgsteal_direct每秒增长数千。

根因:服务器同时运行了监控代理(如Zabbix Agent)和日志收集(Fluentd),两者合计占用了大量内存。Redis的used_memory接近物理内存上限,分配缓冲区时触发direct reclaim。

修复:

  1. 设置swappiness=1(允许文件页回收但尽量不swap匿名页,减少Redis匿名页被swap-out)
  2. 修改Redis配置:maxmemory=6GB(留2GB给系统和其他进程)
  3. 调整overmemory-policy=allkeys-lfu(内存满时自动淘汰冷key)
  4. 调整Redis的oom_score_adj=-1000(保护Redis不被OOM杀死)
  5. 最终:direct reclaim消失,延迟恢复到0.5ms以内

6.2 案例二:容器OOM引发的服务雪崩

现象:Kubernetes集群中某个Node上,多个Pod因OOM被杀死,引发服务雪崩。

诊断:kubectl describe pod显示Last State: OOMKilled。dmesg -T显示大量"Memory cgroup out of memory"日志。Node内存监控显示使用率最高85%,但某些Pod的RSS突然激增。

根因:

  • Pod的memory.limit设置为2Gi,但应用因内存泄漏导致RSS缓慢增长至接近limit
  • 当RSS达到limit时,触发cgroup OOM
  • OOM Killer杀死的进程恰好是应用的主进程(Java主线程),触发kubelet重启
  • 重启后紧接着又OOM → CrashLoopBackOff

修复:

  1. 短期:将memory.limit提高到4Gi,同时设置resources.requests=2Gi
  2. 中期:通过heap dump分析Java堆内存泄漏
  3. 长期:引入sidecar容器的memory.max限制,并为关键服务设置memory.min保证
  4. 监控告警:配置PSI监控,设置avg10 > 30时触发告警

6.3 案例三:数据库系统的zone_reclaim_mode调优

现象:MySQL服务器在64GB内存的2路NUMA机器上运行,但性能不如预期。

诊断:numastat -m显示大量Node 1的内存跨节点访问,perf stat -e node-load-misses,node-store-misses计数极高。

行级检查:cat /proc/sys/vm/zone_reclaim_mode 返回0,说明未启用zone reclaim。MySQL工作集约50GB,跨两个NUMA节点随机访问,大量远程访问带来延迟惩罚。

修复:

  1. 设置zone_reclaim_mode=1:内存分配优先从本地节点回收,减少跨节点访问
  2. 配置innodb_buffer_pool_size=48GB,确保缓冲池完全位于本地节点
  3. 使用numactl --interleave=all启动MySQL(均匀分布内存)
  4. 监控改进后node-load-misses降低60%,TPS提升30%

七、性能调优核心参数清单

参数说明推荐场景
/proc/sys/vm/swappiness磁盘swap积极性数据库=1, 默认=60, zram=150-200
/proc/sys/vm/vfs_cache_pressuredentry/inode缓存回收权重数据库=50, 文件服务器=100
/proc/sys/vm/min_free_kbytes最低保留空闲内存大内存服务器=262144
/proc/sys/vm/zone_reclaim_modeNUMA本地回收数据库=1, 普通=0
/proc/sys/vm/extfrag_threshold内存碎片避免阈值THP=100, 默认=500
/proc/sys/vm/laptop_mode延迟磁盘写入笔记本=5, 服务器=0
/proc/sys/vm/page-clusterswap预读页数默认=3, SSD=0~1
/proc/sys/vm/oom_dump_tasksOOM时打印所有task调试=1, 关闭=0

THP(Transparent Huge Pages)与页面回收

THP(透明大页)使用2MB大页代替4KB小页,减少TLB miss和页表开销。但在内存碎片化场景下,THP经常因无法分配连续2MB空间而失败(khugepaged碎片整理开销大)。数据库场景通常建议关闭THP:

# 关闭THP
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# 某些数据库(如MongoDB)在碎片场景下开启THP反而更高效
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

八、可观测工具全景

工具用途
vmstat实时显示swap/内存/block活动
sar -r历史内存使用统计
numastatNUMA内存分布统计
slabtopSLAB/SLUB分配器实时状态
perf c2cNUMA缓存一致性分析
bpftrace -e 'kprobe:oom_kill_process'OOM事件动态追踪
cat /proc/pressure/memoryPSI内存压力
cat /sys/fs/cgroup/memory/*/memory.usage_in_bytescgroup内存使用
dmesg | grep -i oom历史OOM日志

九、总结

Linux页面回收与OOM Killer构成了内存管理的最后防线,核心要点可以归纳为:

LRU设计:双链表(Active/Inactive)+ Referenced位实现了近似LRU,区分匿名页(需swap)和文件页(可直接丢弃)。Linux 5.15+ 的mgLRU进一步提升了大规模系统的LRU效率。

回收路径:kswapd异步回收(WMARK_LOW触发)是首选;direct reclaim(WMARK_MIN触发)是同步痛苦路径;OOM Killer是所有手段失败后的最后手段。系统设计应尽量避免触发direct reclaim和OOM。

swap策略:swappiness平衡匿名页与文件页回收比例;用zram替代磁盘swap可以减少I/O等待;合理的swap使用不代表系统不健康——适度swap可以将冷内存让给热缓存。

OOM防护:oom_score_adj保护关键服务(-1000禁止杀死);cgroup v2 memory.max和memory.high提供分级保护;PSI提前预警避免OOM发生。

生产必做:设置合理的swappiness和min_free_kbytes;为关键服务配置oom_score_adj;使用PSI监控内存压力;在cgroup中为容器memory.limit留有余量。

物理内存终究有限,Page Reclaim就是把有限的资源分配给最需要的人的艺术。理解它,才能在内存风暴中守住性能的底线。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部