内存管理易混点清单
这篇不讲新的知识点,主要整理内存管理里最容易混淆的边界问题。
很多时候不是完全不会,而是:
- 知道这些名词
- 也能说出大概意思
- 但一旦放到同一道题里,就容易串线
所以这篇的重点不是“扩知识”,而是“压边界”。
一、TLB miss 和缺页中断不是一回事
容易混的点
很多人会把它们直接画等号,觉得:
TLB miss = 缺页
这是不对的。
正确区分
TLB miss
TLB miss 只是说明:
TLB 这个地址映射缓存里,没有当前虚拟页对应的页框号缓存项。
这时候还要继续去查页表。
缺页中断
缺页中断表示:
查页表之后发现,这次访问当前无法直接完成,需要操作系统介入处理。
最常见的情况是:
- 页当前不在内存中
也可能是:
- 第一次访问合法匿名页,还没真正分配物理页框
- 写时复制时需要分裂新页
一句话记忆
TLB miss 是缓存未命中;缺页中断是页访问无法直接完成。
二、canonical address 和“地址合法”不是一回事
容易混的点
有人会把 canonical 理解成:
- 对当前进程来说合法
- 能正常访问
这不准确。
canonical 的本质
在 x86-64 里,canonical address 指的是:
这个虚拟地址的格式是否符合 CPU 规定。
也就是说,它首先是一个“地址形式是否合法”的问题。
地址合法的本质
地址合法说的是:
这个地址对当前进程来说,是否落在一个有效的虚拟内存区域里,而且访问权限也合理。
所以二者不在一个层面上。
一句话记忆
canonical 只是地址格式合法,不代表对当前进程一定可访问。
三、“地址合法”“已映射”“已在内存” 不是一回事
这三个词很容易在答题时串掉。
1. 地址合法
表示:
这个地址对当前进程来说,属于有效虚拟内存区域,且访问方式权限允许。
2. 已映射
表示:
这片虚拟地址已经建立了映射关系。
也就是说,OS 已经知道这段地址将来应该对应什么。
3. 已在内存
表示:
这一页当前真的驻留在物理内存里,有可用物理页框。
关键区别
- 地址合法,不代表已经在内存里
- 已映射,不代表已经在内存里
- canonical,也不代表这个地址对当前进程就是合法的
一句话记忆
合法不等于在内存,已映射也不等于已驻留。
四、共享地址空间、共享页表、共享物理页不是一回事
1. 共享地址空间
表示:
多个执行流看到同一套用户虚拟地址空间。
最典型的情况是同一进程内的多个线程。
2. 共享页表
表示:
多个执行流真的在使用同一套页表结构 / 同一个
mm_struct。
这在线程之间通常成立。
3. 共享物理页
表示:
不同虚拟页最终映射到同一个物理页框。
这可以发生在线程之间,也可以发生在不同进程之间,比如共享内存、fork 后写时复制之前的父子进程。
最关键的结论
- 线程通常共享地址空间,也共享页表
fork后父子进程通常不是共享页表,而是页表分开、物理页先共享- 共享物理页不代表共享页表
一句话记忆
线程常共享页表;父子进程常是页表分开、物理页先共享。
五、malloc、brk、mmap 不是同一层级
容易混的点
很多人会把这三个词当成并列关系。
正确区分
malloc:用户态库函数,是分配器对外暴露的接口brk:扩展传统 heap 的底层机制mmap:建立独立映射区的底层机制
更准确地说
malloc 本身并不是系统调用。它通常会:
- 先从分配器已有空闲块里切空间
- 不够时再通过
brk或mmap向 OS 要更多虚拟地址空间
一句话记忆
malloc 是接口,brk/mmap 是向 OS 要虚拟地址空间的方式。
六、malloc 成功返回,不等于物理页已经分配好
容易混的点
有人会把 malloc 成功理解成:
- 这块内存已经完全准备好了
- 物理页也已经在 RAM 中了
这不准确。
正确理解
malloc 成功返回,说明的是:
当前进程拿到了一段合法可用的虚拟地址空间。
但很多时候:
- 具体物理页框还没分配
- 第一次访问这段内存时,才按需分配页框
- 或者通过缺页中断建立实际映射
一句话记忆
malloc 返回的是虚拟地址,不等于物理页已经分配到位。
七、页表不是一次性全部建好的
容易混的点
很多人会以为:
- 进程启动时,整棵页表都已经完整建好了
正确理解
更常见的情况是:
- 顶层页表和一部分必要结构先存在
- 很多下级页表和叶子页表项会按需补建
- 某片地址真正被访问到时,才创建对应下级页表或建立最终映射
一句话记忆
页表结构通常是部分先有,部分按需补。
八、“页不在内存”不等于“一定在 swap”
容易混的点
有人会说:
只要页不在 RAM,就肯定在 swap
这不对。
正确理解
页当前不在内存,可能有很多情况:
- 这页还没真正分配物理页框
- 这页来自文件,将来可以再从文件读回来
- 这页曾经在内存里,后来被换到了 swap
- 这页只是一个尚未访问的合法匿名页
一句话记忆
不在内存,不一定就在 swap。
九、Java 中“相互引用”不等于一定内存泄漏
容易混的点
很多人会说:
Java 两个对象互相引用,所以 GC 回收不了
这不准确。
正确理解
只要这两个对象:
- 虽然互相引用
- 但整体已经和 GC Roots 不可达
那它们仍然会被回收。
Java 中更典型的泄漏场景
- 静态集合长期持有对象
- 缓存无限增长
- 监听器没注销
ThreadLocal没清理- 长生命周期对象错误持有短生命周期对象
一句话记忆
Java 泄漏的关键不是“互相引用”,而是“明明没用了却仍然可达”。
十、内存泄漏、内存溢出、栈溢出要分清
1. 内存泄漏
该回收的没回收。
2. 内存溢出
想申请的申请不到。
3. 栈溢出
栈空间超过上限。
三者关系
- 内存泄漏可能导致内存溢出
- 内存溢出不一定由内存泄漏引起
- 栈溢出通常单独讨论,不等于堆 OOM
一句话记忆
泄漏是原因之一,溢出是结果表现,栈溢出是另一类内存空间耗尽。
十一、分页缓解的是外部碎片,不是所有碎片
容易混的点
有人会说:
分页解决了碎片问题
这不完整。
正确理解
- 分页主要缓解外部碎片
- 但最后一页用不满时,仍然可能产生内部碎片
一句话记忆
分页缓解外部碎片,但仍可能有内部碎片。
十二、工作集不是“进程拥有的全部页”
正确理解
工作集指的是:
一个进程在某一段时间内,频繁访问、真正活跃使用的那部分页面集合。
不是全部页,而是当前最常用、最离不开的那部分页。
抖动的本质
如果物理内存装不下活跃进程的工作集,就会:
- 刚换进来的页很快又被换出去
- 刚换出去的页马上又要换回来
- 系统大量时间都花在缺页、换页和磁盘 IO 上
一句话记忆
工作集是当前常用页;抖动是工作集装不下导致的疯狂换页。
十三、最后给一个总记忆框架
如果你总觉得概念容易串,可以按下面这个顺序去想:
- 地址格式是否合法 → canonical
- 这个地址对当前进程是否合法 → 地址合法
- 这片虚拟地址有没有建立映射 → 已映射
- 当前这页是否真的在 RAM 里 → 已在内存
- TLB 里有没有映射缓存 → TLB hit / miss
- 页表能不能直接完成访问 → 正常访问 / 缺页 / 权限异常
只要这个顺序稳了,很多概念就不容易再混。
十四、总结
内存管理最容易混的地方,通常不在定义本身,而在这些概念彼此的边界:TLB miss 和缺页中断、canonical 和地址合法、合法与映射与驻留、共享地址空间与共享页表与共享物理页、malloc 与 brk 与 mmap、泄漏与溢出与栈溢出。把这些边界压实之后,整条内存管理主线会清晰很多,回答问题时也会明显更稳。
