80年代640K内存对哪个人都够用了。那时微软开发的还是DOS os,程序员们还在想如何压榨完有限的640K内存。 而现在,随便一个笔记本都16G内存了,比那时多了一万倍。那当时这种言论是无稽之谈吗?为何觉得这么小内存就够了呢?
在运行这些可执行文件时,是通过装载器解析ELF或PE格式的可执行文件。
装载器会将对应指令和数据加载到内存,让CPU去执行。
要实现装载到内存,则装载器需满足:
如何才能满足如上需求呢?
可在内存里,找到一段连续内存空间,然后分配给装载的程序,然后把这段连续的内存空间地址,和整个程序指令里指定的内存地址做个映射:
程序里有指令和各种内存地址,但只需关心虚拟内存地址。 任一程序,它看到的都是同样的内存地址。我们维护一个虚拟内存到物理内存的映射表,这样实际程序指令执行时,会通过虚拟内存地址,找到对应物理内存地址,然后执行。 因为是连续内存地址空间,所以只需维护映射关系的:
找出一段连续的物理内存和虚拟内存地址进行映射的方法。这里的段指系统分配出来的那个连续的内存空间。
这很好,解决了程序本身无需关心具体物理内存地址的问题,但也有不足,如
比如电脑有1GB内存,先启动一个 图形渲染,占了512MB内存,接着启动个Chrome,占了128MB内存,再启动一个Py程序,占了256MB内存。
现在关掉Chrome,空闲内存还有:
讲道理,已有足够的空间再装载个200MB程序。但这256MB内存空间并非连续,而是被分成两段128MB内存。于是实际上我们的程序无法被加载进来。
如何解决这个问题呢?
可将Py程序所用256MB内存写到硬盘,再从硬盘读回到内存。读回时,不再把它加载到原来位置,而是紧跟在那已被占用的512MB内存后。
这就有了连续的256MB内存空间,就能去加载个新的200MB程序。 若你安装过Linux,你肯定遇到过分配一个swap硬盘分区抉择,这块分出的磁盘空间,就是给Linux进行内存交换用的。
虚拟内存、分段,再加上内存交换,三驾马车,看起来已完美解决计算机同时装载运行很多个程序的问题。不过仍有性能瓶颈:
所以,若内存交换时,交换的是个很占内存空间的程序,整个机器会变得卡顿。
既然问题原因是:
则解决方案自然就是想着:
于是内存管理给出了 内存分页(Paging) 方案。
和分段这样分配一整段连续的空间给到程序相比,分页是把整个物理内存空间切成一段段固定尺寸的大小。而对应程序所需占用的虚拟内存空间,也会切成一段段固定尺寸的大小。
这一个个连续&&尺寸固定的内存空间,叫页(Page)。 从虚拟内存 =》物理内存的映射,不再是拿整段连续的内存的物理地址,而是按一个个页,一般来说:
Linux下,通常设置成4KB,可查看:
由于内存空间都是预先划分好的,也就没有不能使用的碎片,而只有被释放出来的很多4K的页。 即使内存空间不够,需要让现有的、正在运行的其他程序通过内存交换释放出一些内存页出来,一次性写入磁盘的也只有少数的一个页或几个页,不会花太多时间,让整个机器被内存交换的过程给卡住。
分页的方式使得加载程序的时候,不再需要一次性把程序加载到物理内存中。 可在进行虚拟内存、物理内存页之间的映射后,并不真的把页加载到物理内存,而是只在程序运行中,需要用到对应虚拟内存页里面的指令和数据时,再加载到物理内存。
os也就是这么做的,当要读取特定的页,却发现数据并未加载到物理内存,就会触发来自CPU的缺页错误(Page Fault)。 os会捕捉到这个错误,然后将对应页,从存放在硬盘上的虚拟内存读出,加载到物理内存。 这使得可以运行那些远大于我们实际物理内存的程序。同时,任何程序都无需一次性加载完所有指令和数据,只需加载当前所需的。
通过虚拟内存、内存交换和内存分页技术组合,最终得到了一个让程序无需考虑实际物理内存地址、大小和当前分配空间的解决方案。 这些技术和方法,对于我们程序的编写、编译和链接过程都是透明的。这也是我们在计算机的软硬件开发中常用的一种方法,就是加入一个间接层。
通过引入虚拟内存、页映射和内存交换,程序不再需考虑对应真实内存地址、程序加载、内存管理等问题。 任一程序只需把内存当成是一块完整而连续的空间来直接使用即可,其它都是 os 保姆帮你做好的。
在虚拟内存、内存交换和内存分页这三者结合之下,要运行一个程序,“必需”的内存是很少的。CPU只需要执行当前的指令,极限情况下,内存也只需要加载一页就好了。 再大程序,也可分成一页。每次,只在需要用到对应的数据和指令的时候,从硬盘上交换到内存里面来就好了。 以现在4K内存一页的大小,640K内存也能放下足足160页呢,也无怪乎在比尔·盖茨会说出“640K ought to be enough for anyone”。
不过硬盘的访问速度比内存慢很多,所以我们现在的计算机没有几G内存都不好意思卖。
除了程序分页装载这种方式之外,还有其他优化内存使用的方式:“动态装载”,请见后文分解。
在Java这样使用虚拟机的编程语言里,程序是怎么装载到内存里的?也和本文一样,通过内存分页和内存交换的方式加载到内存里面来的么?
JVM已是上层应用,无需考虑物理分页,一般更直接是考虑对象本身空间大小,物理硬件管理统一由承载JVM的os解决。
参考