打开编辑器、浏览器和终端时,每个程序都像拥有可自由使用的内存。实际上,主存由所有进程和操作系统共同使用,容量有限,访问还必须受到保护。内存管理要解决的核心问题是:让程序使用稳定、连续的地址,同时把有限的物理内存分配给当前真正需要的数据。
本课承接进程与主存的基础知识,从虚拟地址讲到页、缺页、常驻内存和交换,再用 Linux 的 free、top 观察系统。学习目标不是记住所有字段,而是建立可以解释现象的关系:申请空间不等于数据已经进入主存,缺页不等于发生故障,空闲内存少也不等于系统内存不足。
为何要地址空间
程序运行时会访问指令、变量和缓冲区。这些内容都需要地址。假如程序直接使用物理地址,装入位置改变后,原来记录的地址就可能失效;多个程序还可能写到相同位置,互相破坏数据。仅靠程序约定“不要越界”无法保护系统,因为程序会有错误,也可能故意访问别人的数据。
操作系统为每个进程提供地址空间。它是进程可以使用的地址及其访问规则的集合,其中可能有代码区、堆、栈和文件映射区域。这里的地址称为虚拟地址,主存中的实际位置使用物理地址。程序通常通过虚拟地址访问内存,硬件在操作系统建立的规则下完成转换,同时检查访问是否被允许。
可以把地址空间理解为每个进程各自使用的编号系统。进程甲中的地址 0x4000 和进程乙中的地址 0x4000,虽然写法相同,却不必指向相同的物理位置。这与不同楼栋都可以有“房间 101”相似:必须先确定属于哪个进程,地址才有明确含义。实际转换依据映射关系完成,并非给虚拟地址加上固定偏移就总能得到结果。
地址空间还允许保留没有映射的区域。某个地址可以落在进程的地址范围内,却没有合法用途;也可能已映射但只允许读取。程序写入只读代码区,或者访问没有权限的区域时,系统会阻止访问。常见的“段错误”就可能来自这种非法访问,不能把它解释成“物理内存已经用完”。
隔离并不排斥共享。操作系统可以让不同进程的部分虚拟地址指向相同的物理内容,例如只读的公共代码;也可以在明确授权后共享可写数据。地址空间解决的是访问关系与保护问题,共享关系仍由映射和权限决定。理解这一点,也就能理解为什么把多个进程的内存数字直接相加,有时会重复计算共享部分。
虚拟地址如何映射到物理内存
连续的虚拟地址,不要求背后有连续的物理内存。内存管理把地址空间切成固定大小的块,称为页;把物理内存切成相同大小的块,称为页框。页描述虚拟空间中的块,页框描述容纳它的物理位置。某个虚拟页可以放在任意合适的页框中,相邻虚拟页也可以放在相距很远的页框里。
本课用 4 KiB 作为页大小的教学示例,实际系统可能使用其他页大小。若地址为 9000 字节,页大小为 4096 字节,则它位于虚拟页号 2,页内偏移为 808 字节,因为 9000 = 2 × 4096 + 808。若虚拟页号 2 映射到页框号 7,物理地址就是 7 × 4096 + 808 = 29480 字节。转换改变页所在的位置,页内偏移保持不变。

保存这些映射及访问状态的信息通常称为页表。入门阶段只需知道它回答哪些问题:这个虚拟页是否合法,能否读取或写入,当前是否驻留在物理内存,以及驻留时对应哪个页框。真实处理器会加速地址转换,但本课不展开内部层级和加速结构。程序仍按自身的地址访问数据,无需知道某页放在主存的哪块位置。
沿着交互中的高亮路径,先选择进程,再拆分地址,最后查看映射结果。特别注意:切换进程后,相同虚拟地址可以得到不同结果;页内偏移却不会因为映射到不同的页框而改变。
虚拟地址空间的大小与实际可用内存也不是同一个概念。进程可以保留很大的地址范围,其中许多页暂时没有驻留。系统因此能给程序提供整齐的地址视图,并按实际访问逐步安排主存。但地址范围足够大,并不保证未来每次写入都有足够资源;申请成功后的实际使用仍会受到内存、交换空间和系统分配策略的约束。
以图像处理为例,程序申请 256 MiB 缓冲区后,如果只写入开头 8 MiB,保留的虚拟空间可以明显大于这段操作新增的常驻内存。实际变化受分配器、页大小和系统行为影响,不能把“申请了多少字节”直接当作“新增了多少物理内存”。排查问题时应把地址范围与已经驻留的内容分开观察。
页与缺页:访问为什么可能停下来
页既是映射的基本单位,也是许多分配和回收动作的基本单位。程序哪怕只修改页中的少量字节,也可能需要为整页安排页框。反过来,已经申请的区域如果从未被实际访问,就可能还不需要独占物理页框。地址空间提供访问位置,按页处理让系统可以逐步兑现程序对内存的需求。

当处理器执行内存访问,发现当前映射状态不能直接完成访问时,会触发异常并交给操作系统处理,其中常见情况称为缺页。这里的“缺”指访问所需的页尚不能按当前映射直接使用,不必然表示硬件损坏,也不必然表示剩余主存不足。系统需要先判断访问是否合法,再决定能否补齐条件并继续。
合法的首次访问是常见来源。程序得到可用地址范围后,首次写入某页时,系统可能为它分配页框、准备清零的内容并建立可写映射。这个过程不一定需要读取磁盘。如果该页对应文件内容,而内容已经在内存缓存中,也可能直接建立映射。这类不需要等待存储读入的缺页,通常归入次缺页,即 minor fault。
另一种情况需要从存储设备取回内容。例如,程序首次读取尚未缓存的文件页,或者访问之前换出到交换空间的页。需要等待存储读入的缺页通常归入主缺页,即 major fault。它往往更昂贵,但不同设备、缓存状态和并发负载会改变等待时间,不能给每次主缺页套用固定耗时。
还有些异常根本不能通过调入内容解决:地址非法,或者当前写入违反访问权限。系统可能向进程发送信号,使程序终止;部分权限异常也可能由系统合法处理后恢复,例如需要创建私有副本的写入。判断关键是访问规则与具体原因,不能看到“缺页”就认定需要读交换空间。

如果访问合法,系统准备好内容、更新映射后,通常会让原先的指令重新执行。对程序来说,读取仍然得到需要的数据;对性能来说,中间多了可能很长的停顿。缺页计数因此是观察线索,需要与发生阶段和等待时间结合。启动阶段出现许多次缺页可能很正常,稳定运行阶段持续出现主缺页并伴随延迟升高则值得调查。
例如,服务启动时申请并写入 64 MiB 区域,按 4 KiB 页估算,涉及 16384 页。逐页首次写入可能产生大量次缺页,但这不能单独证明内存紧张。若同样的服务进入稳定期后不断把刚换出的数据读回来,同时存储读写繁忙,问题就更接近活跃数据无法同时留在主存。统计次数相似,含义却可能不同。
工作集、RSS 与交换:留下什么,移走什么
进程在某段时间内真正频繁使用的页,可以用工作集来描述。它是与时间窗口相关的概念,不是程序启动后访问过的所有页。编辑器刚打开大文件时可能集中读取文件内容,随后只反复处理当前可见区域;这 2 个阶段的工作集不同。讨论工作集时,应同时说明观察窗口和正在执行的任务。
RSS 是 Resident Set Size,即常驻集大小,表示进程当前驻留在物理内存中的页所占的量。Linux top 中的 RES 可以用于观察这一量级。RSS 包含当前较热和较冷的驻留页,也可能包含共享映射。它是常驻状态的统计,不能直接回答“接下来 1 分钟内最需要哪些页”,因此不能与工作集画等号。
可以把工作集看作近期活动需求,把 RSS 看作当前驻留结果。某些页很久未用却尚未被回收,会让 RSS 大于近期工作集;某些近期需要的页已经被换出,则可能在下次访问时再次调入。在压力变化和任务切换时,两者关系也会变化。普通命令通常不会直接提供准确的工作集数字,需要结合访问行为和性能变化估计。

内存紧张时,操作系统尝试回收当前价值较低的页。对于能从文件重新取得、且没有修改的干净文件页,可以丢弃内存中的副本;再次访问时再从文件读取。已修改的文件页通常需要先写回。匿名内存,例如堆中的私有数据,不能随便丢弃;如果允许交换,就可能把其中较冷的内容移到交换空间,以腾出页框。
交换空间常称为 swap,可以位于交换分区或交换文件中。本课把它理解为安置部分内存内容的后备位置。它给回收增加选择,却不能把存储设备变成同等速度的主存。换出和换入都需要成本;如果某页很快又被访问,刚释放的页框可能又要被重新占用。这里的决定因素是页的活跃程度,而不只是它属于哪个进程。
当活跃页的总需求长期超过能分配给它们的物理内存时,系统可能频繁换入换出,大量时间花在搬运页上,真正执行任务的进度变慢,这种现象称为抖动。鼠标还会移动、进程也没有退出,却不代表服务能按时完成请求。主存容量、访问局部性和并发规模共同决定系统是否接近这种状态。
下面的滑块以“工作集中的页当前都希望驻留”为简化假设,比较物理预算、进程常驻内容与近期工作集。它用于理解数量关系,不模拟真实内核的替换算法。观察时先固定工作集,只增加冷页;再固定预算,逐步增大工作集,比较这 2 种变化的含义。
还有容易混淆的边界:文件缓存被回收后再次读取,会造成存储访问,但不一定出现交换读写。因此,swap used 没增长不能证明没有内存压力;反过来,已经占用部分 swap 也不能证明系统正在抖动。必须同时关注当前读写速率、可用内存与业务延迟,才能从“存着多少”走到“正在发生什么”。
观察 free 与 top:先看量级,再看变化
free 回答系统整体内存如何分布,top 帮助定位进程和活动变化。以下是 Linux 上的教学示例,具体字段与计算口径可能随版本不同。为了容易核对,先用 free -m 查看 MiB 量级;如果使用 free -h,要同时读取显示的单位,避免把 GiB 与 MiB 的数字直接比较。
$ free -m
total used free shared buff/cache available
Mem: 8192 3072 512 128 4608 4352
Swap: 2048 256 1792
total 表示系统报告的主存总量,free 表示当前完全未使用的部分。系统会把暂时不用的内存用于缓存文件,以减少后续存储访问,所以 free 很小并不自动说明容量不足。available 估计在不依赖交换的情况下,还能为新应用提供多少内存,包含预计可回收的部分,因此更适合用于初步判断余量。
示例中,完全空闲的主存只有 512 MiB,但预计可用量有 4352 MiB,约为 4.25 GiB。若业务响应稳定,也没有持续换页,就不应仅凭 free 那列判断需要扩容。available 仍然是估计值,缓存也并非都能立刻无代价回收;当写回积压或访问模式变化时,还要观察实际延迟和其他指标。
不要把输出中的所有列相加。shared 与其他分类存在交叉,不是额外独立的主存;不同版本对 used 的计算也可能调整。教学示例里 used + free + buff/cache 恰好等于 total,实际解释仍应以本机工具的字段定义为准。建立正确的概念关系,比从每列数字拼出看似精确的结论更有用。

$ top
PID COMMAND VIRT RES SHR %MEM
2104 image-worker 4096m 1536m 128m 18.8
这里的 VIRT 是进程虚拟内存规模,可以包括保留区域、文件映射和已经换出的部分;RES 是常驻物理内存量;SHR 是可能共享的常驻部分,不能简单当成“其他进程确实替它分担的容量”。%MEM 通常根据常驻量与系统物理内存总量计算。表中约 1.5 GiB 的常驻量,相对于 8 GiB 主存约为 18.8%。
看到 VIRT 为 4 GiB,不能说该进程已经独占 4 GiB 主存;看到 RES 为 1.5 GiB,也不能说终止它就必定能立即多出 1.5 GiB 可用量。共享页、缓存及其他系统活动都会影响变化。多个进程的 RES 求和还可能重复统计共享页,因此它适合定位较大的进程,不适合直接充当整机物理内存的分账总表。
有效观察需要时间轴。先记录稳定负载下的请求量、available、关键进程 RES 和业务延迟,再在负载变化后观察它们如何联动。短时间可以用 vmstat 1 10 辅助观察,其中 si、so 常用于查看交换读入与写出速率,单位以本机工具说明为准。首行通常反映启动以来的平均值,后续行才更适合分析采样期间的变化。
若 available 持续下降、关键进程 RES 持续增长,值得定位进程内的数据保留;若 available 偏低,si、so 在后续采样中持续活动,且响应变慢,就需要评估工作集与物理预算是否匹配。单独某个采样点可能碰巧遇到缓存填充或批处理峰值,连续记录才能区分瞬时现象和持续趋势。
常见坑:把 swap 当成可直接相加的容量

例如,批处理程序保留 2 GiB 很久不用的中间结果,而当前只反复访问 1 GiB 数据,适当交换冷页可能对主要阶段影响有限。另有程序在每轮计算中扫描全部 10 GiB 数据,运行环境只能为它提供 6 GiB 物理预算,就可能不断替换仍会很快使用的页。相同的交换容量,在不同访问模式下会表现出完全不同的性能。
也不能把“关闭 swap”当作通用优化。没有交换空间时,匿名页的回收选择减少,内存不足可能更早表现为分配失败或进程被终止;有交换空间时,又可能以明显停顿暴露压力。是否配置、配置多少,应结合负载、存储能力、延迟目标和故障处理方式决定。本课的判断重点是观察当前后果,而不是推导统一配置值。
还有 1 个常见误区是追求把 free 提高到很大。为了清空缓存而主动丢弃有用内容,可能导致后续重新读取文件,使业务更慢。缓存是系统利用空余内存的正常方式。需要警惕的是不可回收或频繁被重新使用的数据挤压可用量,并造成等待;不能把“图表看起来用了很多”直接等同于浪费。
容量不足时,应先控制峰值来源。例如减少同时处理的大对象数量,限制请求队列,按块读取文件,避免多个阶段同时保留完整副本。确认数据生命周期合理后,再评估是否增加物理内存。仅扩大 swap 可能推迟进程终止,却把失败方式变成延迟失控,仍然无法达到服务要求。
案例:持续上涨来自泄漏,还是缓存
某图片服务在 8 GiB 主存机器上运行,启动后 RES 为 600 MiB,处理请求后逐渐升至 2 GiB。工程师怀疑泄漏,但只有起点和终点不足以下结论。图片缓存、分配器保留空间、并发缓冲区以及真正失去释放机会的对象,都可能推高常驻量,需要通过受控比较缩小解释范围。
先固定请求并发和图片大小,重复访问相同的 1000 张图片,记录每轮结束后的 RES、应用记录的缓存字节数、待处理任务数和响应时间。再让任务队列排空,保留进程继续观察。如果缓存大小和 RES 先增长后趋于稳定,符合缓存预热的解释;如果相同输入每轮都让长期保留量增加,就更需要追查对象生命周期。

随后使用应用支持的方式收紧缓存上限,比较相同负载下的变化。若缓存统计稳定受限,而其他长期保留对象不断增加,泄漏假设得到更多支持。若取消上限后内存才随不同图片不断增长,则可能是缓存策略缺少容量约束。后者即使数据仍能被程序找到,也可能造成同样严重的内存压力,不能用“不是泄漏”作为容量合理的证明。
还要解释释放后的现象。应用释放对象后,内存分配器可能把空间留给进程后续复用,未立即归还系统,因此 RES 不一定马上下降。若下轮相同负载能够复用已有空间、峰值不再持续提高,这与无界累积不同。仅靠 top 无法证明某个对象是否已经释放,还需结合应用的对象数、分配统计或适当的分析工具。
如果业务请求量也在增长,必须先排除并发带来的正常扩张。例如每个在途任务临时需要 32 MiB,额外增加 20 个同时执行的任务,就可能增加约 640 MiB 需求,尚未计算共享部分和其他开销。把内存变化与在途任务数对照,有时比寻找泄漏更快接近原因。过长队列还可能让等待中的数据一直被引用,形成持续保留。
最终报告应写明证据边界:在固定输入与并发下,缓存是否到达上限,任务排空后哪些对象仍保留,重复运行的峰值是否稳定,系统是否出现交换和延迟恶化。启动后上涨并不是泄漏证据,停机重启后下降也不是根因结论。只有把数据的生命周期、驻留状态和时间趋势连接起来,才能选择修复缓存、限制并发或排查泄漏。
命令走查:从 free 到延迟的对照记录
把前面的判断变成一次可复查的记录。仍以这台 8 GiB 主存的图片服务为例,先选定同一台机器、同一个服务进程和固定的图片集合。每个负载阶段保持一分钟,记录实际请求量、在途任务数以及该分钟的响应时间第九十五百分位,即 p95。它表示约百分之九十五的请求耗时不超过这个值,比只看平均耗时更容易发现部分请求的等待。以下数字是教学示例,不是上线阈值。
在每个阶段开始时记下时间,执行下面的命令;把 2104 替换成实际进程号。top 取两次快照用于观察常驻量,vmstat 连续采样一分钟。业务延迟从服务监控读取,并对齐这段采样的起止时间。命令输出各自保留,之后再按时间合并,不能把不同阶段的数字拼成同一行。
date '+%F %T %z'
free -m
top -b -d 1 -n 2 -p 2104
vmstat -S K 1 61
free -m
date '+%F %T %z'
这里用 free -m 的 MiB 和 top 中核对单位后的 RES 记录容量;vmstat -S K 的交换速率按本机说明确认单位,本例记为 KiB/s。跳过 vmstat 首行,用后续60行计算 si、so 的平均值,同时保留原始行检查是否持续活动。开始与结束的两次 free 用来观察阶段内可用量如何变化;它们不能代表中间的最低值。
| 阶段与分钟窗口 | 在途任务数 | available,MiB,起→止 | RES,MiB,起→止 | si / so 均值,KiB/s | p95,毫秒 |
|---|---|---|---|---|---|
| 稳定基线,10点整 | 8 | 4300 → 4200 | 1500 → 1550 | 0 / 0 | 80 |
| 提高并发,10点零二分 | 40 | 1500 → 600 | 3600 → 4300 | 128 / 2048 | 260 |
| 维持并发,10点零四分 | 40 | 500 → 350 | 4400 → 4450 | 4096 / 3072 | 950 |
| 降回并发,10点零六分 | 8 | 1800 → 2600 | 3200 → 2800 | 64 / 0 | 100 |
基线说明低并发时余量充足。提高并发后,可用量收缩、进程常驻量增加,随后持续换入换出且延迟继续升高,支持“活跃数据需求超过当前物理预算”的解释。此时常驻量增长已经放缓,性能却继续恶化,说明只盯着 RES 是否上涨会漏掉问题。降回并发后余量和延迟恢复,又为并发相关的内存压力提供了证据;仍需对照存储等待、其他进程和请求量,不能仅凭相关性认定唯一原因。
| 同一窗口里的证据 | 当前判断 | 下一步动作 |
|---|---|---|
| free 很低,available 稳定,交换速率接近零,延迟稳定 | 缓存占用可能正常 | 保留基线,继续观察峰值 |
| available 持续下降,交换持续活跃,延迟升高 | 内存压力可能影响服务 | 降低并发,对照恢复情况 |
| available 下降,交换为零,延迟升高 | 仍可能有文件页回收或其他瓶颈 | 检查文件读取、存储等待和进程变化 |
| swap 已用量仍高,交换速率回落,延迟恢复 | 历史换出内容尚未全部调回 | 观察当前活动,无需仅为清空数值重启 |
这份记录应附上图片大小、请求量与并发调整时间。若换了输入或同时启动备份任务,比较条件就发生了变化,需要单独标记。先建立能重复的证据,再决定限制并发、调整缓存或增加主存,才能检验措施是否真正改善了目标延迟。
放进方案里
再写测量口径。用关键进程的常驻量描述进程规模,用系统可用内存描述整机余量,同时记录交换读写与业务延迟。明确采样间隔、单位和负载阶段,保留启动、预热、稳定运行及峰值结束后的曲线。虚拟内存规模用于理解地址空间,不直接写成主存需求;多个进程存在共享页时,也不把常驻量之和当作整机的精确账单。
方案应说明数据如何退出内存。缓存要有容量约束和淘汰条件,任务队列要有长度边界,大文件处理尽量限制同时驻留的数据块。对于泄漏嫌疑,安排固定输入的重复运行,比较任务排空后的对象统计与后续峰值。若释放后常驻量没有立即下降,继续检查空间是否被复用,不能仅凭曲线形状下结论。
最后写压力下的动作。出现可用量持续收缩、交换读写持续活跃并伴随延迟上升时,先限制新增并发或暂停非关键批处理,再按预定条件扩容或转移负载。阈值与持续时间应来自目标负载测试,不能直接照搬其他机器。交换空间的作用、存储等待风险和内存不足时的恢复步骤需要明确;它可以缓冲部分冷页需求,但不能替代对热数据容量的验证。验收既要看峰值是否容纳得下,也要看压力解除后能否恢复稳定,以及服务延迟是否始终满足约定。
小结与自测
虚拟地址提供进程看到的访问位置,物理地址对应主存中的位置。页与页框让连续的虚拟空间可以落在分散的物理位置上。缺页意味着当前访问需要系统介入处理,其中有些只需安排页框,有些需要等待存储,也有些访问不合法。把这些情况分清,才能理解缺页次数与性能之间的关系。
RSS 描述当前驻留,工作集描述某段时间内的活跃需求,交换空间提供部分内容的后备位置。阅读工具时,先核对单位和统计范围,再比较同一时间窗口内的变化。available、进程常驻量、交换速率和业务延迟共同构成证据;任何单列数字都不足以完整描述系统是否健康。
可以用以下问题检查理解。每个回答都应包含判断依据,而不只是记住术语。
- 进程甲和进程乙都读取虚拟地址
0x4000,为什么可能得到不同内容?因为转换使用各自地址空间的映射,地址写法相同不代表映射相同。 - 首次写入新缓冲区时缺页计数增加,是否说明发生交换?不能。系统可能只是在分配页框和建立映射,需要查看是否有存储读入及交换活动。
- 机器只剩 512 MiB 完全空闲,但预计可用量为 4352 MiB,是否应立即扩容?不能只凭空闲量判断,还要看可回收部分、持续趋势与业务表现。
- 固定输入下常驻量上涨后进入平台,能否证明完全没有泄漏?不能。平台支持有限缓存或复用的解释,但仍需观察时间范围、对象统计及其他负载。
- 增加 swap 后进程不再退出,却频繁超时,方案是否已经达标?没有。存活只满足部分要求,还需验证活跃数据的驻留能力和延迟目标。
后续阅读 H12 时,可以带着“哪些内容需要从主存之外取回”理解存储设备的作用;进入 S5 时,可以继续追踪文件读写与内存中数据之间的关系。掌握本课的区分后,主存占用、文件缓存和存储等待就能放进相互联系的解释中,而不是只看某个数字是否变大。
下一课转向 S5 文件系统入门:把本章验收过的结论带进后续主题,写成可检查的方案约束。