内存一紧,系统就开始做取舍题。页面替换算法干的事不复杂:内存页塞满了,新页又要进来,总得挑一个旧页让位。可挑谁、按什么标准挑,直接决定了系统是轻微卡一下,还是频繁抖动。FIFO、LRU、时钟这三种策略,名字听着都挺正经,真放到业务里各有各的短板,没有哪个能包打天下。

页面替换算法没有完美答案
先把这个概念放平。操作系统把内存切成固定大小的页,程序访问的页不在内存里,就触发缺页中断,得从磁盘或者交换区把页搬进来。内存框用完了,页面替换算法就要选一个受害者。评价它不能只看命中率,还得看实现开销、最差情况的表现、对访问模式的敏感度。很多教程喜欢列公式,实际排障时你会发现,访问模式一变,结果就翻脸。
FIFO:简单不代表安全
FIFO 是最早进来的页先出去,实现起来就是一个队列,入队出队,代码短得让人放心。它的短板也直白:年龄老不等于没用。一个页可能早就加载进来,但一直有人访问,FIFO 照样可能把它踢走,下一次访问又得重新搬。更别扭的是 Belady 异常,某些访问序列下,给的内存框更多,缺页反而更多。这个现象不是理论玩具,循环扫大数组时就容易撞上。队列操作本身不贵,但选错受害者的代价会落到磁盘 I/O 上,系统看起来就像突然发呆。
LRU:记得太细也有代价
LRU 按最近最少使用来淘汰,思路很符合直觉:刚被访问过的页留着,很久没动的先走。命中率通常比 FIFO 好看,尤其是热点集中的业务。可它要维护每个页的访问顺序,时间戳、链表、哈希表都得上,访问一次记一次账。单核时代这点开销还能忍,多核并发下光是同步这些记账结构就够喝一壶。很多系统最后用的是近似 LRU,不是不想精确,是精确太贵。还有个坑,循环访问的范围比内存大,LRU 也会被转晕,刚淘汰的页马上又被请回来,来回折腾。
时钟策略:折中不等于万金油
时钟策略算是折中派,给每个页一个引用位,指针转圈扫,碰到引用位是 1 的就给第二次机会,清零后跳过;碰到 0 的就淘汰。它不用维护完整的时间顺序,开销比精确 LRU 小,行为又比 FIFO 讲道理,所以不少真实系统愿意用它或者它的变种。短板在于,引用位这东西很粗,只能说明这段时间内被碰过,说明不了先后。如果一轮扫描里所有页的引用位都被置上,时钟策略就会退化成 FIFO,转一圈回到原点,还是按老顺序踢人。指针扫得太快或者太慢也麻烦,太快近似太粗,太慢又跟不上内存压力。
虚拟主机场景,感受比参数更真实
把镜头拉到虚拟主机或者小内存 VPS 上,这些短板就不是纸面概念了。共享环境里内存配额本来就不宽裕,宿主机一紧张,页面替换和交换一起来,租户这边就是后台点一下卡半天、SSH 回显一顿一顿。你看到的可能只是某个页面打开慢,底层其实是内存页在被反复搬进搬出。这个时候光调 PHP 内存上限没用,得看进程的实际常驻内存、缓存策略,还有是不是有定时任务在半夜扫全表。虚拟化层有自己的内存回收机制,具体行为以服务商的面板和官方说明为准,别拿一篇老文章硬套。
真要落地,先做三件事
第一,把访问模式摸清楚。是热点集中,还是大范围顺序扫,还是周期性的批处理,这三种情况对策略的偏好完全不同。第二,别迷信命中率单指标,看看缺页带来的等待时间、交换区读写、系统负载,这些更接近用户感受。第三,能上近似实现就别硬追精确 LRU,尤其是并发高的服务,记账结构的锁竞争可能比多几次缺页更伤。
还有一点常被忽略:页面替换算法只是内存管理的一环,不是万能开关。程序本身的内存泄漏、缓存无上限、连接池开太大,都会把内存推到替换策略救不了的地步。先把这些低级问题排掉,再谈调算法,顺序别搞反。
如果你现在正被小内存机器折腾,先看三样:进程常驻内存有没有异常增长,交换区是不是在持续读写,缓存有没有设上限。三样都正常,再去动页面替换相关的参数;三样里有一项冒红,先修业务代码和配置,别急着换策略。页面替换算法能帮你兜底,但它不是用来给内存泄漏擦屁股的。
【版权声明】:本站内容若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!