早期内存策略
早期计算机中,程序是直接运行在物理内存上,程序所访问的地址都是物理地址。那么在计算机在运行多个程序时,有限的物理内存是如何分配的?
假设一个场景:
物理内存大小:128MB 程序A:10MB 程序B:100MB 程序C:20MB
场景:同时运行程序A和程序B
内存前10MB分配给程序A,10-110MB的空间分配给程序B
上述的方式就可以实现程序A和B的同时运行,但是这种简单的分配策略有很多问题:
- 地址空间不隔离,容易被意外或恶意访问
- 内存使用效率低,需要大量换入换出
- 程序运行地址不确定,程序在编写时,访问数据和指令跳转时的目标地址都是固定的。
计算机名言: Any problem in computer science can be solved by another layer of indirection. 计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
解决上述问题的方式就是再增加一个中间层,使得进程间接的访问内存。中间层给进程一个虚拟的地址,然后通过某些方式将这些地址映射到实际地址中。
只要中间层能保证映射过程能被正确处理,那么进程和进程之间的地址就可以妥善的被隔离。
分段映射

此方式解决了问题一和问题三,但问题二依旧没有被解决。
程序局部性原理 当一个程序在运行时,在某个时间段内,它只是频繁地用到了一小部分数据。
因此,另外一种更小粒度的内存分割和映射的方式就被人们设计出来,使得程序的局部性原理得到了充分的利用,提高了内存的使用率。这种方式就是分页
分页
分页机制使得程序在逻辑上连续,物理上离散。在现代操作系统上分页大小一般都是4KB。
基本流程:
Process1的VP2和VP3不在内存中,当内存需要这两个页的时候,硬件就会捕获这个页错误消息,然后操作系统将VP2&3从硬盘读取出来,装载到内存中,建立映射关系。
共享内存:
比如物理内存的PP3页。
虚拟存储的存储依赖硬件的支持。对于CPU来说,有专门MMU(Memory Management Unit)来处理页映射。
扩展 页大小为4kb,会导致整个页表的数量非常大,仅32位系统上就需要100多万个页表项(4GB/4KB),为此Linux还提供了多级页表和大页两个机制,来解决页表项过多的问题。
查看内存的使用情况
free命令
kylin@kylin-thinkpad-t14-gen-1 ~> free总计 已用 空闲 共享 缓冲/缓存 可用内存: 7798672 4401072 153240 321292 3244360 2769200交换: 9357308 399872 8957436
- total:总内存大小;
- used:已使用内存的大小,包含了共享内存:
- free:未使用内存的大小;
- shared:共享内存的大小;
- buffer/cache:缓存和缓冲区的大小;
- available:新进程可用内存的大小。
Swap分区:一块被操作系统当做内存来使用的磁盘,操作系统会将进程暂时不用的数据存储到磁盘中。进程需要这些内存时,再将这些数据读到内存中(换入),Swap性能通常较差。 buffer/cache:
free显示的是操作系统整体内存的使用情况,需要查看进程内存的使用情况时,还可以通过top/ps/htop这些命令来查询。
htop命令

- VIRT:进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内。
- RES:常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括Swap和共享内存。
- SHR:共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。
- %MEM:进程使用物理内存占系统总内存的百分比。
注意:
- 虚拟内存并不会全部分配物理内存
共享内存并不一定是共享,程序的的代码段、非共享的动态链接库也被计算在内。
虚拟内存空间分布

只读段:代码和常量等。
- 数据段:全局变量等。
- 堆:动态分配的内存,从低地址开始向上增长。
- 文件映射段:动态库、共享内存等,从高地址开始向下增长。
- 栈:局部变量和函数调用的上下文等。栈的大小是固定的,一般是8MB。
内存的分配和回收
内存的分配
malloc()是标准的C库内存分配函数,对应系统调用有两种实现方式:
- brk()
- mmap()
小块内存(小于128K),C库使用brk()分配,brk()函数的内存分配方式是移动堆顶,并且内存释放后不会归还系统而是缓存起来,这样就可以重复使用。
大小内存,C库使用mmap()分配,在文件映射段找一块空闲内存分配出去
优缺点
brk减少了缺页异常,但是由于内存没有归还系统,在内存繁忙场景下会造成内存碎片
mmap会在释放时将内存归还系统,因此每次mmap都会发生缺页异常。内存工作繁忙时,频繁的内存分配会导致大量的缺页异常
注意:
以上两种调用发生后,都只在首次访问的时候才会真正分配内存,也就是通过缺页异常进入内核,再由内核分配内存。
内存的回收
- LRU算法回收缓存:回收最近使用少的内存页面
- 回收不常访问的内存(换出):把不常用的内存写到swpa(磁盘)里
- 通过OOM杀死进程
OOM: 内核的一种保护机制,根据进程的处理器使用情况和内存使用情况给进程计算出oom_score分数。 分数越高,越容易被OOM杀死。分数可以在/proc下,手动设置进程oom_adj手动设定分数
参考
- 《程序员的自我修养》
