虚拟内存与寻址方式解析(面试版)


文章目录


引言

虚拟内存和物理内存的区别?它们之间是怎么对应的?段式、页式、段页式分别是怎么寻址的?这是一个非常经典的操作系统底层问题,对于后端开发(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)>

  • 寻址过程

    1. 根据段号 去查段表 (Segment Table)

    2. 段表中记录了该段的基址 (Base Address)段长 (Limit)

    3. 越界检查 :检查 偏移量 是否超过了 段长,超过则报错(Segfault)。

    4. 计算物理地址

      物理地址 = 段基址 + 偏移量 物理地址 = 段基址 + 偏移量 物理地址=段基址+偏移量

  • 优缺点

    • 优点:符合程序员思维,方便代码共享和保护。
    • 缺点外部碎片。因为段的大小不一,内存中会留下许多塞不进新段的小空隙。

页式存储 (Paging) ------ 按固定大小分块

核心思想 :将物理内存和虚拟内存都切成固定大小 的块(通常是 4KB)。物理内存块叫页框 (Page Frame) ,虚拟内存块叫页 (Page)

  • 虚拟地址结构

    < 页号 ( V P N ) , 页内偏移量 ( O f f s e t ) > <页号 (VPN), 页内偏移量 (Offset)> <页号(VPN),页内偏移量(Offset)>

  • 寻址过程

    1. 根据页号 去查页表 (Page Table)

    2. 页表中记录了该页对应的物理页框号 (PPN)

    3. 计算物理地址:直接拼接。

      物理地址 = 物理页框号 × 页大小 + 偏移量 物理地址 = 物理页框号 \times 页大小 + 偏移量 物理地址=物理页框号×页大小+偏移量

    • 注:实际硬件中通常是直接拼接二进制位。
  • 优缺点

    • 优点:解决了外部碎片问题,内存利用率高。
    • 缺点内部碎片(最后一页没填满);页表可能很大(需要多级页表);频繁访问内存(需要 TLB 加速)。

现代主流 OS(Linux, Windows)主要基于页式管理(尽管 x86 硬件保留了分段机制,但 Linux 主要将其旁路掉,只用分页)。

段页式存储 (Segmented Paging) ------ 集大成者

核心思想 :先将程序按逻辑分段 ,再把每个段分页。这是为了结合两者的优点。

  • 虚拟地址结构

    < 段号 , 段内页号 , 页内偏移量 > <段号, 段内页号, 页内偏移量> <段号,段内页号,页内偏移量>

  • 寻址过程(需要三次内存访问,比较慢,必须依赖硬件缓存):

    1. 根据段号段表 ,得到该段的页表起始地址

    2. 根据段内页号页表 ,得到物理页框号

    3. 计算物理地址

      物理地址 = 物理页框号 + 页内偏移量 物理地址 = 物理页框号 + 页内偏移量 物理地址=物理页框号+页内偏移量

总结

这张图表展示了三种寻址方式的对比

方式 单位大小 碎片类型 寻址复杂度 适用场景
段式 不定(按逻辑) 外部碎片 早期系统,便于共享保护
页式 固定(如 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),本质上是修改页表,将文件直接映射到用户态的虚拟内存地址,绕过内核缓冲区拷贝。

相关推荐
代码or搬砖1 小时前
SQL核心语法总结:从基础操作到高级窗口函数
java·数据库·sql
無量1 小时前
AQS抽象队列同步器原理与应用
后端
月明长歌1 小时前
【码道初阶】【Leetcode94&144&145】二叉树的前中后序遍历(非递归版):显式调用栈的优雅实现
java·数据结构·windows·算法·leetcode·二叉树
杰克尼2 小时前
蓝桥云课-5. 花灯调整【算法赛】
java·开发语言·算法
wanghowie2 小时前
01.02 Java基础篇|核心数据结构速查
java·开发语言·数据结构
乂爻yiyao2 小时前
java并发演进图
java
java1234_小锋2 小时前
Redis6为什么引入了多线程?
java·redis
努力学算法的蒟蒻2 小时前
day38(12.19)——leetcode面试经典150
算法·leetcode·面试
9号达人2 小时前
支付成功订单却没了?MyBatis连接池的坑我踩了
java·后端·面试