Multi-Gen LRU(Multi-Generation Least Recently Used)是 Linux 内核为解决传统 LRU 算法在大内存场景下的低效问题 而引入的内存回收优化机制,核心目标是提升内存页活跃度判断的精准度和回收效率。该算法首次在 5.18 内核 中正式合入主线,后续在 5.19+ 版本中持续优化,4.4/5.4 等老内核无原生支持(需通过 backport 补丁移植)。
传统 LRU 算法的痛点
传统 Linux 内核的内存回收依赖单一代际的 LRU 双向链表 ,将物理页分为 active_list(活跃页)和 inactive_list(非活跃页),核心逻辑是:
- 页被访问时,从
inactive_list提升至active_list; - 内存不足时,
kswapd从inactive_list尾部回收页; - 通过
page_referenced()函数扫描页的访问位,判断是否需要重新激活。
大内存场景下的核心痛点
在大内存**(如 128GB+)** 场景中,物理页数量可达 数千万甚至数亿级(按 4KB 页计算,128GB 对应 3200 万页),传统 LRU 的设计缺陷会被急剧放大:
全局链表扫描效率极低
这是传统 LRU 最核心的瓶颈。
- 线性扫描的开销 :
kswapd回收内存时,需要线性遍历inactive_list,判断每个页是否可回收(如是否为脏页、是否被锁定)。大内存下链表长度可达百万级,单次扫描会消耗大量 CPU 周期,导致回收延迟高达数百毫秒甚至秒级。 - 访问位检测的额外开销 :为了判断页的活跃度,内核需要调用
page_referenced()扫描页对应的所有页表项 (支持多进程共享的页),检查硬件的访问位(PG_referenced)。大内存下,这个操作的时间复杂度会随页数量线性增长,进一步加剧 CPU 消耗。 - 极端情况 :当系统内存紧张时,
kswapd会长期占用 CPU(出现kswapd高负载),导致用户态进程被抢占,业务响应延迟增加。
全局锁竞争激烈
传统 LRU 依赖 全局 lru_lock 保护 active_list 和 inactive_list 的操作(如添加、删除、迁移页)。
- 多核场景的冲突 :在多 CPU 服务器中,多个进程并发申请 / 释放内存、触发页访问时,都会竞争
lru_lock。大内存下页操作的频率更高,锁冲突会导致严重的上下文切换,降低系统整体吞吐量。 - 锁粒度问题:全局锁的粒度太大,无法并行处理不同区域的页,完全无法发挥多核 CPU 的优势。
活跃度判断精准度不足
传统 LRU 的双链表模型无法区分页的 "短期活跃" 和 "长期活跃",导致两种误判:
- 误回收 "低频重要页" :例如,某些后台服务的页可能几小时才访问一次,会被降级到
inactive_list并被回收。当服务再次访问时,会触发大量缺页异常(Page Fault),需要从磁盘 / 交换分区读取数据,导致服务卡顿。 - 误保留 "短期活跃页" :某些一次性任务的页(如临时文件缓存)会被短暂访问并晋升到
active_list,长期占用内存,导致真正需要内存的进程无法获取资源。
内存回收的 "饥饿" 问题
大内存场景下,kswapd 的回收速度可能跟不上内存消耗速度 ,导致系统触发 OOM Killer。
- 当
kswapd扫描速度过慢,内存水位持续低于min,内核会认为 "内存回收无效",直接启动 OOM Killer 杀死占用内存大的进程。 - 这种情况在高并发业务(如数据库、大数据计算)中尤为致命,可能导致核心服务被意外终止。
对系统性能的具体影响
在大内存服务器(如 256GB 内存的数据库主机)上,传统 LRU 的缺陷会直接体现在以下指标上:
- CPU 利用率异常 :
kswapd线程的 CPU 占比可达 20%~50%,挤占业务进程的资源。 - 页缺失率升高 :误回收导致大量主缺页异常(Major Page Fault),磁盘 IO 飙升(
iostat显示rMB/s突增)。 - 响应延迟增加:业务进程因内存不足被阻塞,或因缺页异常等待磁盘 IO,导致接口响应时间从毫秒级升至秒级。
- 系统稳定性下降:频繁触发 OOM Killer,或因锁竞争导致进程死锁、内核软锁(Soft Lockup)。
Multi-Gen LRU 的核心设计思想
Multi-Gen LRU 的核心是将物理页按 "访问代际" 分层管理,通过代际差异快速区分页的活跃度,从而精准、高效地回收 "冷页"。
核心概念:代际(Generation)
Multi-Gen LRU 将所有物理页划分为多个代际,每个代际用一个独立的链表管理,代际编号越大,代表页的 "闲置时间越长"(活跃度越低)。
- 代际划分规则 :
- 新分配的页加入第 0 代(最活跃代);
- 当页在一段时间内未被访问,会被晋升到更高代际;
- 若页在高代际被重新访问,则降级回低代际(如第 0 代);
- 内存回收时,优先从最高代际的链表尾部回收页。
- 代际数量 :可通过内核参数
vm.mg_lru_max_gen配置,默认一般为 16~32 代。
核心机制:分层管理与快速老化
页的代际迁移
Multi-Gen LRU 为每个页添加了代际标记 (扩展 struct page 字段),并通过两个核心操作实现代际迁移:
- 老化(Ageing):内核定期扫描各代际的页,若页未被访问,则将其移入更高代际;若被访问,则重置为第 0 代。
- 回收(Reclaim) :
kswapd优先回收最高代际的页,只有当高代际无页可回收时,才会扫描低代际。
这种分层机制避免了传统 LRU 的全局链表扫描,大幅降低了 CPU 开销。
访问位优化:避免全局扫描
传统 LRU 依赖 page_referenced() 遍历页表项的访问位(PG_referenced),而 Multi-Gen LRU 做了两点优化:
- 批量更新访问位 :通过内存管理单元(MMU)的硬件特性(如 Intel 的
PCID、ARM 的ASID)批量刷新页的访问状态,无需逐页扫描; - 代际内局部扫描:仅在当前代际内判断页的活跃度,而非全局链表。
锁粒度优化:从全局锁到代际锁
传统 LRU 使用全局 lru_lock 保护链表操作,Multi-Gen LRU 则为每个代际配置独立的锁,多核场景下不同 CPU 可并行处理不同代际的页,锁竞争大幅减少。
与传统 LRU 的兼容性
Multi-Gen LRU 并未完全替换传统 LRU,而是作为增强功能叠加:
- 保留了
active_list和inactive_list的逻辑,代际管理在inactive_list内部实现; - 支持通过内核参数
vm.mg_lru_enabled动态开关(1 开启,0 关闭)。
Multi-Gen LRU 的关键优势
- 回收效率提升:优先回收高代际冷页,减少无效扫描,降低内存回收的 CPU 占用(实测大内存场景下可降低 30%~50% 回收耗时);
- 活跃度判断更精准:代际分层能区分短期活跃和长期活跃页,减少 "误回收" 现象,提升系统稳定性;
- 多核友好:代际锁机制降低了锁竞争,适合高并发服务器场景;
- 可配置性强:通过内核参数调整代际数量、老化周期等,适配不同硬件和业务场景。
与其他内存回收特性的协同
- 透明巨页(THP):Multi-Gen LRU 支持巨页的代际管理,5.19+ 内核优化了巨页的拆分与回收逻辑;
- Zswap/Zram:与压缩交换分区协同,优先回收高代际页并压缩存储,提升交换效率;
- Page Migration:内存热插拔场景下,Multi-Gen LRU 可优先迁移低代际活跃页,减少服务中断。