文章目录
- 引言
- 虚拟内存与物理内存
- 虚拟内存与物理内存之间是如何对应的?
- 三种寻址方式详解
-
- [段式储存(Segmentation) ------ 按逻辑分块](#段式储存(Segmentation) —— 按逻辑分块)
- [页式存储 (Paging) ------ 按固定大小分块](#页式存储 (Paging) —— 按固定大小分块)
- [段页式存储 (Segmented Paging) ------ 集大成者](#段页式存储 (Segmented Paging) —— 集大成者)
- 总结
引言
虚拟内存和物理内存的区别?它们之间是怎么对应的?段式、页式、段页式分别是怎么寻址的?这是一个非常经典的操作系统底层问题,对于后端开发(Java/Go)来说,理解它是进行性能调优(如排查 OOM、理解 GC 行为、数据库缓存机制)的基石。同时这也是大厂面试的常考题,下面让我们一起探讨。
虚拟内存与物理内存
- 物理内存:物理内存是指计算机中实际存在的硬件内存,由内存条等硬件组成
- 虚拟内存:由操作系统提供,为每个进程创建一个独立的、连续的地址空间 ,空间大小可以远远大于物理内存。每个进程都认为自己拥有独立、连续的内存空间。且每个进程的虚拟空间都是独立隔离的
使用虚拟内存的好处?
- 提供内存隔离:每个进程以为自己独占整个内存空间,互不干扰,这解决了多进程之间地址冲突的问题;
- 支持比物理内存更大的地址空间(例如32位系统支持4GB虚拟地址空间,即使物理内存只有2GB),因为程序运行符合局部性原理,CPU 访问内存会有很明显的重复访问的倾向性,对于那些没有被经常使用到的内存,我们可以把它换出到物理内存之外,比如硬盘上的 swap 区域;
- 实现按需加载 (Demand Paging)和内存保护;
- 简化程序开发,程序员无需关心物理内存布局。
简单来说,物理内存是硬件真相,虚拟内存是操作系统编织的"谎言"(抽象层)。
| 特性 | 物理内存 (Physical Memory) | 虚拟内存 (Virtual Memory) |
|---|---|---|
| 本质 | 真实的硬件设备(RAM 内存条)。 | 操作系统提供的逻辑抽象层。 |
| 大小 | 受限于硬件容量(如 16GB, 32GB)。 | 受限于 CPU 字长(32位系统4GB,64位系统理论极大)。 |
| 地址空间 | 全局唯一,所有程序共享这块硬件。 | 进程隔离。每个进程都认为自己拥有独立、连续的内存空间。 |
| 可见性 | 操作系统内核和硬件直接访问。 | 应用程序(Java/Go 进程)直接看到的内存。 |
| 主要作用 | 存储实际的数据和指令。 | 隔离进程、简化内存管理、实现内存超售(Swap/Pagefile)。 |
后端视角: 当你在 Java 中 new Object() 或在 Go 中 make([]int) 时,分配的地址(如 0x4f00123)全都是虚拟地址。你永远无法直接触碰物理内存地址
虚拟内存与物理内存之间是如何对应的?
虚拟内存和物理内存之间的桥梁是 MMU(Memory Management Unit,内存管理单元) 。这是一个集成在 CPU 中的硬件单元。
- 映射表: 操作系统在内存中维护着映射表(页表或段表),记录了"虚拟地址 X 对应 物理地址 Y"。
- 转换过程: 当 CPU 执行指令要访问某个虚拟地址时,MMU 会自动查询映射表,将其转换为物理地址,然后发给内存条读取数据。
- 缺页中断 (Page Fault): 如果 MMU 发现数据不在物理内存中(可能在磁盘 Swap 分区里),会触发中断异常,操作系统会把数据从磁盘加载到物理内存,更新映射表,然后重试
三种寻址方式详解
不同的操作系统采用不同的方式来管理这种映射,主要分为段式、页式和段页式。
段式储存(Segmentation) ------ 按逻辑分块
核心思想 :按照程序的逻辑结构划分。比如把程序分为"代码段"、"数据段"、"栈段"。每一段的大小是不固定的。
-
虚拟地址结构:
< 段号 ( S e g m e n t S e l e c t o r ) , 段内偏移量 ( O f f s e t ) > <段号 (Segment\ Selector), 段内偏移量 (Offset)> <段号(Segment Selector),段内偏移量(Offset)>
-
寻址过程:
-
根据段号 去查段表 (Segment Table) 。
-
段表中记录了该段的基址 (Base Address) 和 段长 (Limit) 。
-
越界检查 :检查
偏移量是否超过了段长,超过则报错(Segfault)。 -
计算物理地址:
物理地址 = 段基址 + 偏移量 物理地址 = 段基址 + 偏移量 物理地址=段基址+偏移量
-
-
优缺点:
- 优点:符合程序员思维,方便代码共享和保护。
- 缺点 :外部碎片。因为段的大小不一,内存中会留下许多塞不进新段的小空隙。
页式存储 (Paging) ------ 按固定大小分块
核心思想 :将物理内存和虚拟内存都切成固定大小 的块(通常是 4KB)。物理内存块叫页框 (Page Frame) ,虚拟内存块叫页 (Page) 。
-
虚拟地址结构:
< 页号 ( V P N ) , 页内偏移量 ( O f f s e t ) > <页号 (VPN), 页内偏移量 (Offset)> <页号(VPN),页内偏移量(Offset)>
-
寻址过程:
-
根据页号 去查页表 (Page Table) 。
-
页表中记录了该页对应的物理页框号 (PPN) 。
-
计算物理地址:直接拼接。
物理地址 = 物理页框号 × 页大小 + 偏移量 物理地址 = 物理页框号 \times 页大小 + 偏移量 物理地址=物理页框号×页大小+偏移量
- 注:实际硬件中通常是直接拼接二进制位。
-
-
优缺点:
- 优点:解决了外部碎片问题,内存利用率高。
- 缺点 :内部碎片(最后一页没填满);页表可能很大(需要多级页表);频繁访问内存(需要 TLB 加速)。
现代主流 OS(Linux, Windows)主要基于页式管理(尽管 x86 硬件保留了分段机制,但 Linux 主要将其旁路掉,只用分页)。
段页式存储 (Segmented Paging) ------ 集大成者
核心思想 :先将程序按逻辑分段 ,再把每个段分页。这是为了结合两者的优点。
-
虚拟地址结构:
< 段号 , 段内页号 , 页内偏移量 > <段号, 段内页号, 页内偏移量> <段号,段内页号,页内偏移量>
-
寻址过程(需要三次内存访问,比较慢,必须依赖硬件缓存):
-
根据段号 查段表 ,得到该段的页表起始地址。
-
根据段内页号 查页表 ,得到物理页框号。
-
计算物理地址:
物理地址 = 物理页框号 + 页内偏移量 物理地址 = 物理页框号 + 页内偏移量 物理地址=物理页框号+页内偏移量
-
总结
这张图表展示了三种寻址方式的对比
| 方式 | 单位大小 | 碎片类型 | 寻址复杂度 | 适用场景 |
|---|---|---|---|---|
| 段式 | 不定(按逻辑) | 外部碎片 | 中 | 早期系统,便于共享保护 |
| 页式 | 固定(如 4KB) | 内部碎片 | 低(硬件易实现) | 现代主流 OS(Linux) |
| 段页式 | 固定 | 内部碎片 | 高(多次查表) | 复杂系统架构 |
在日常后端开发,哪里有内存管理与寻址的知识运用呢?
-
GC 与 内存整理 :Java 的 GC 算法(如 G1, ZGC)在处理堆内存时,实际上是在整理虚拟内存。底层的操作系统会通过页表映射,尽可能让物理内存的使用变得紧凑,但如果 Java 堆过大导致频繁 Swap,性能会急剧下降。
-
Copy-On-Write (COW) :Linux
fork()子进程(如 Redis RDB 快照)时,子进程和父进程共享物理内存,只有在写操作时,OS 才会利用页表机制复制一份物理页。理解页式存储能帮你理解为什么 Redis 快照时内存占用不会瞬间翻倍。 -
mmap :在 Go 或 Java 中使用
MappedByteBuffer(Zero Copy),本质上是修改页表,将文件直接映射到用户态的虚拟内存地址,绕过内核缓冲区拷贝。