本文整理操作系统的内存虚拟化(虚拟内存、页表、MMU、TLB、大页)、缓存置换算法,以及垃圾回收(GC)的核心原理。
内存资源始终稀缺,进程对内存的需求不断增长,且进程间需要内存隔离。历史上出现过 Swap 技术(不执行的进程整体换出到磁盘),但存在碎片和频繁切换问题。最终沉淀出虚拟内存方案:
虚拟内存为进程提供理论上极大的地址空间。64 位 CPU 可寻址 (2^{64}) 个地址,实际操作系统允许进程使用的地址空间远小于该上限,通常为几十到几百 TB 量级(如 x86-64 Linux 常规为 128 TiB,启用 5 级页表后可达 128 PiB)。
操作系统将虚拟内存分块,每块称为一个页(Page);物理内存同样分块,称为 Frame。Page 到 Frame 的映射由**页表(Page Table)**维护。

虚拟地址到物理地址换算分 3 步:
例:页大小 4K,访问地址 100,000:
换算在 CPU 内的**内存管理单元(MMU)**中完成。CPU 发出虚拟地址,MMU 查页表算出物理地址并经地址总线访问内存。
(配图缺失:MMU 位置)
大地址空间若用一级页表,条目数巨大(如 1G 空间需 256K 条目)。采用二级页表:进程内部用大页(如 4M)管理,OS 仍用 4K 页。MMU 先查一级再查二级,顶级页表只需创建已用部分,大幅降低进程创建成本。可递归扩展至 3、4 级。

MMU 查页表需访问内存,速度慢。为此 MMU 内集成转置检测缓冲区(TLB,快表),缓存 Page Number → Frame Number 映射。局部程序反复访问相同地址,几十个 TLB 条目即可获得高命中率;多核 CPU 每核心有独立 TLB(如 i7:L1 TLB 64 条、L2 TLB 1024 条)。
缓存行号 = PageNumber % N,不支持 LRU 类置换,命中率受限。应用内存需求大(如 1G)而默认 4K 页时,页数达 26 万,TLB 易冲突。Linux 2.6+ 提供 Huge Page,可显著减少页数、提升 TLB 命中。开启:
sudo sysctl -w vm.nr_hugepages=2048Java 可用 -XX:+UseLargePages 启用。适合内存需求大或高并发抗流量的场景。
缓存命中(Hit)/穿透(Miss)。平均响应 ≈ M·T + L(M 穿透率、T 穿透代价、L 缓存延迟)。目标是将未来最低频使用的数据置换出去。
利用页表访问位/脏位,定时清零,优先置换读写位均为 0 的页。简单有效、被广泛使用。
第二次机会算法:FIFO 置换前检查读位,读位为 1 则移到队尾并清 0(多给一次机会)。可用循环链表实现,省去节点从队首搬到队尾的开销。
置换最久未使用的数据。常见实现:双向链表(使用则移到表头,置换从表尾删除)+ 哈希表查询(如 Java LinkedHashMap)。缺点是高并发场景维护链表+哈希开销大。
描述"最近":用页表读位累加计数得到使用频次(LFU),但 LFU 不会遗忘旧热点。改进为 Aging(老化)算法:用 8 位计数器 A,每 Tick 将 A 右移一位,把读位 R 置于最高位。有访问 A 升、无访问 A 降,无访问则归 0,从而用数学描述"最近"。硬件实现 Aging 可高效模拟 LRU。
GC 不仅是回收模块,往往是应用实际的内存管理者,负责向 OS 申请/释放内存、向应用分配内存、标记回收垃圾、按应用特性动态优化。核心指标存在权衡(不可兼得):
并发度越高,拆分/同步/空间开销越大;暂停时间越短,GC 与应用交替越频繁。
每个对象维护引用计数,归 0 即回收。问题:
从根对象(Root,如栈上指针、全局对象)出发遍历引用链,不可达对象即垃圾,可正确处理循环引用,且容错性好(下次扫描可纠正)。标记-清除与三色标记均属于此类。
白=待回收,黑=保留。所有对象初始染白;从 rootSet 对每个 Root 递归 DFS 标记其引用对象为黑;最后遍历 heapSet 回收仍为白色的对象。缺陷:用户程序删除黑色对象引用后产生浮动垃圾,需下次 GC 处理。
标记-清除产生碎片,需 Compact 将存活对象挤压到连续空间。观察:新对象死亡概率高、老对象更持久。据此划分区域(以 Java 为例):
三区域死亡概率递减、存活周期递增,均用三色标记-清除。
内存不足时,可降低吞吐量(GC 时间上升)或增加暂停时间。多数语言提供吞吐/暂停阈值参数;内存充裕时建议提高阈值、延后 GC 以提升整体效率。