一文带你了解物理内存和虚拟内存

2025-11-16 08:50:45

只会写代码的程序员就像一名司机,会踩油门、打方向盘、能将车子从A开到B。而实际开发中的内存溢出、内存泄漏、以及性能优化问题,就像驾驶途中出现了爆胎或者交通堵塞问题,如果不对其机理有所了解,则会陷入茫然无措的境地。

所以今天就和霞姐一起学习一下物理内存和虚拟内存的相关知识,加深一下内力吧!

一、物理内存

1.物理内存是什么?

通俗来说,物理内存就是主板上实际安装的、可由内存控制器直接寻址的内存条。

物理内存真实存在,并且打开机箱就能看到。

在linux系统上,用户可以使用free -h命令,或者dmidecode -t memory查看物理内存的详细信息。

2.物理内存的局限性

物理内存出现的很早。20世纪50年代的电子计算机,存储系统已经采取了磁芯+磁鼓两级结构。主存为磁芯(今天的RAM),辅存是磁鼓(今天的Disk)。CPU只能对主存寻址。

只有物理内存的时候,程序员很苦。程序员需要设计出合理的方案,将程序划分成多个块,并调度这些块(被称为段或页)在两级存储之间的传输(被称为覆盖或交换)。

以矩阵乘法为例,如果在不考虑内存容量的情况下,只需要数行代码即可实现。

但如果内存无法同时容纳三个矩阵的情况下,程序员就需要确定矩阵哪些行和列需要被装载进内存,制定将其调入主存的策略,并在程序中插入相应的代码实现,这不仅带来代码量的膨胀,也非常耗时。

因此50年代首批操作系统的设计者就梦想着通过自动化管理所有存储操作,来减轻程序员的负担。

二、虚拟内存

1.动机和历史

让程序受物理内存数量的限制是非常不方便的。不仅要考虑物理内存容量限制、还会带来内存碎片化、多程序之间做不到安全隔离从而导致的崩溃等问题。

计算机的世界里,有一句名言,那就是剑桥大学教授David Wheeler说的:“All problems in computer science can be solved by another level of indirection.计算机科学领域的任何问题都可以通过增加一个间接中间层来解决。”

虚拟内存就是用来解决前述问题的关键技术。

1959年,曼彻斯特大学的阿特拉斯团队成功研制出首个可运行的虚拟内存原型。该原型包括三项关键技术:(a) 能自动将处理器生成的每个地址转换为其当前对应的内存位置的硬件设备(b) 由地址转换器触发的中断机制,可将缺失的数据页调入主内存的请求分页技术(c)能识别并将利用率最低的页送回辅助内存的替换算法。

60年代后,虚拟内在商业操作系统中得到广泛应用,IBM 360/67、CDC 7600、Burroughs 6500、RCA Spectra/70 以及 GE 645 等机型均配备了虚拟内存。

70 年代中期,IBM 370、DEC VMS、DEC TENEX 和 Unix 等系统也相继采用了虚拟内存技术。

2.虚拟内存是什么?

虚拟内存是对主存的抽象,它提供了三个重要的能力:

(1)对主存的高速利用:将主存看成是一个存储在磁盘上的地址空间的高速缓存。在主存中,只保存活动区域,并在主存和磁盘之间来回传送数据

比如:

在玩大型游戏时,游戏文件是装载磁盘上的。不是所有的游戏都会加载到主存中,只有当前运行所需部分才会被加载到主存中。

有一些代码比如错误处理,只有很少的机会被运行到,如果常驻主存,将是对空间的浪费。

(2)简化内存管理:为每个进程提供了一致、私有的地址空间

它定义了一个连续的虚拟地址空间,每个进程都认为自己在独享主存。这降低了程序员编程的难度。

(3)增强了安全隔离:保护每个进程的地址空间不被其它进程破坏

通过虚拟内存机制,用户能以和主存接近的速度,和磁盘相近的价格,获得“主存+磁盘”大小的内存容量。

3.虚拟寻址

(1)CPU通过生成一个虚拟地址来访问主存

(2)CPU内的MMU(内存管理单元)利用存放在主存中的、由OS管理的查询表(页表)来将虚拟地址动态翻译成物理地址。

(3)MMU根据物理地址访问主存

(4)主存返回物理地址存储的数据给CPU

4.Linux的虚拟内存系统

Linux为每个进程维护了一个如下图所示的虚拟地址空间。

(1)内核虚拟内存

包括内核中的代码和数据结构。

某些区域被映射到所有进程共享的物理页面,比如所有进程共享的内核的代码和全局数据结构。

(2)进程虚拟内存

这个大家都很熟悉了,在我前一篇文章中也有类似的图。

Linux将虚拟内存组织成一些区域(area,也叫段segment)的集合。代码段、数据段、堆、共享库段、用户栈都是不同的区域。虚拟地址空间是可以有间隙的。

当MMU试图翻译某个虚拟地址时,触发缺页后,会进入到内核的缺页处理程序。

缺页程序会判断虚拟地址是否合法、对它进行的内存访问是否合法,如果不合法则触发相应的异常;合法的话,则进行换出换入等正常缺页处理。

三、对程序员的启示

学习了物理内存和内存相关的知识后,霞姐列举了三点对程序员的启示,大家也可以发散思维想想看哦~

1.利用局部性原理,写出locality好的代码:尽量让数据访问集中在一块连续的内存区域,这样可以提高缓存命中率,减少缺页异常。

2.警惕内存泄漏:因为虚拟内存的存在,内存泄漏可能不会立即导致程序崩,但会不断消耗物理内存,久而久之堆里就会充满垃圾,这对不会终止的守护进程类的程序来说更加严重。

3.理解malloc/new的成本:申请虚拟内存是“廉价”的,但第一次写入时触发的缺页异常和分配物理内存是有成本的。在性能敏感的代码中,可以考虑使用内存池来预分配内存。