3 内存分页
既然问题原因是:
- 内存碎片
- 内存交换的空间太大
则解决方案自然就是想着:
- 怎么少出现内存碎片
- 当需内存交换时,让需交换写入或从磁盘装载的数据更少一点
于是内存管理给出了 内存分页(Paging) 方案。
和分段这样分配一整段连续的空间给到程序相比,分页是把整个物理内存空间切成一段段固定尺寸的大小。而对应程序所需占用的虚拟内存空间,也会切成一段段固定尺寸的大小。
这一个个连续&&尺寸固定的内存空间,叫页(Page)。
从虚拟内存 =》物理内存的映射,不再是拿整段连续的内存的物理地址,而是按一个个页,一般来说:
页 的 尺 寸 < < 整 个 程 序 的 大 小 页的尺寸 << 整个程序的大小页的尺寸<<整个程序的大小
Linux下,通常设置成4KB,可查看:
- Linux设置的页大小
- macOS 的内存页大小
由于内存空间都是预先划分好的,也就没有不能使用的碎片,而只有被释放出来的很多4K的页。
即使内存空间不够,需要让现有的、正在运行的其他程序通过内存交换释放出一些内存页出来,一次性写入磁盘的也只有少数的一个页或几个页,不会花太多时间,让整个机器被内存交换的过程给卡住。
分页的方式使得加载程序的时候,不再需要一次性把程序加载到物理内存中。
可在进行虚拟内存、物理内存页之间的映射后,并不真的把页加载到物理内存,而是只在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存。
os也就是这么做的,当要读取特定的页,却发现数据并未加载到物理内存,就会触发来自CPU的缺页错误(Page Fault)。
os会捕捉到这个错误,然后将对应页,从存放在硬盘上的虚拟内存读出,加载到物理内存。
这使得可以运行那些远大于我们实际物理内存的程序。同时,任何程序都无需一次性加载完所有指令和数据,只需加载当前所需的。
通过虚拟内存、内存交换和内存分页技术组合,最终得到了一个让程序无需考虑实际物理内存地址、大小和当前分配空间的解决方案。
这些技术和方法,对于我们程序的编写、编译和链接过程都是透明的。这也是我们在计算机的软硬件开发中常用的一种方法,就是加入一个间接层。
通过引入虚拟内存、页映射和内存交换,程序不再需考虑对应真实内存地址、程序加载、内存管理等问题。
任一程序只需把内存当成是一块完整而连续的空间来直接使用即可,其它都是 os 保姆帮你做好的。
4 总结
在虚拟内存、内存交换和内存分页这三者结合之下,要运行一个程序,“必需”的内存是很少的。CPU只需要执行当前的指令,极限情况下,内存也只需要加载一页就好了。
再大程序,也可分成一页。每次,只在需要用到对应的数据和指令的时候,从硬盘上交换到内存里面来就好了。
以现在4K内存一页的大小,640K内存也能放下足足160页呢,也无怪乎在比尔·盖茨会说出“640K ought to be enough for anyone”。
不过硬盘的访问速度比内存慢很多,所以我们现在的计算机没有几G内存都不好意思卖。
除了程序分页装载这种方式之外,还有其他优化内存使用的方式:“动态装载”,请见后文分解。
FAQ
在Java这样使用虚拟机的编程语言里,程序是怎么装载到内存里的?也和本文一样,通过内存分页和内存交换的方式加载到内存里面来的么?
JVM已是上层应用,无需考虑物理分页,一般更直接是考虑对象本身空间大小,物理硬件管理统一由承载JVM的os解决。
参考
- 深入浅出计算机组成原理
- 《程序员的自我修养——链接、装载和库》的第1章和第6章