前言
- 内存的分类:物理内存、虚拟内存、共享内存,它们分别对应top命令输出中的RES、VIRT、SHR三列。
物理内存
- 系统的物理内存被划分为许多相同大小的部分,也称作内存页。
- 内存页的大小取决于CPU的架构和操作系统的配置,一般为4KB。
- 物理内存的使用主要分为以下几方面:
内核使用:
- 操作系统启动时,位于/boot目录下的压缩内核文件会被加载到内存中并解压。这部分内容在系统允许期间都会常驻在内存的起始位置。
进程使用 :
- 驻留内存:进程实际使用的物理内存(包含共享库占用的内存)称为驻留内存,简称RSS(Resident Set Size)。
页缓存(page cache)
- 概念:磁盘io的速度远远低于内存的访问速度,为了加快访问磁盘数据的速度,操作系统使用部分物理内存作为缓存,读写缓存的单位是内存页,故我们把这个缓存称为页缓存(page cache)。页缓存会尽可能的保存着从磁盘读入的数据(现代的操作系统几乎所有可用的物理内存都作为页缓存)。
- 当一个进程准备读取磁盘上的文件内容时,操作系统会先查看待读取的数据所在的页(page)是否在页缓存(pagecache)中,如果存在(命中)则直接返回数据,从而避免了对物理磁盘的 I/O 操作;如果没有命中,则操作系统会向磁盘发起读取请求并将读取的数据页存入页缓存,之后再将数据返回给进程。
- 当一个进程需要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在,则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改过后的页也就变成了脏页,操作系统会在合适的时间把脏页中的数据写入磁盘,以保持数据的一致性。
- Linux 操作系统中的 vm.dirty_background_ratio 参数用来指定当脏页数量达到系统内存的百分之多少之后就会触发 pdflush/flush/kdmflush 等后台回写进程的运行来处理脏页,一般设置为小于10的值即可,但不建议设置为0。与这个参数对应的还有一个 vm.dirty_ratio 参数,它用来指定当脏页数量达到系统内存的百分之多少之后就不得不开始对脏页进行处理,在此过程中,新的 I/O 请求会被阻挡直至所有脏页被冲刷到磁盘中。
- 页缓存占用的内存随时可以释放掉,以便及时腾出更多的物理内存供用户进程使用。
- 优势:用户进程重启,页缓存还是会保持有效,维护页缓存和文件之间的一致性交由操作系统来负责,这样会比进程内维护更加安全有效,同时也极大地简化了代码逻辑。
页缓存中的块缓存(Buffer Cache)
- 磁盘的最小数据单位为扇区(sector),每次读写磁盘都是以sector为单位对磁盘进行操作。sector大小跟具体的磁盘类型有关,有的为512Byte, 有的为4K Bytes。
- 为了减少直接读取磁盘的低效访问,尽可能的提升磁盘访问性能,操作系统使用部分物理内存作为缓存,缓存部分sector数据在内存中,读写缓存的单位是块block(sector的整数倍),我们把这个缓存称为块缓存(buffer cache)。
- 当有数据读取请求时,他能够直接从缓存中将对应数据读出。
- 当有数据写入时,他可以直接再内存中直接更新指定部分的数据,然后再通过异步方式,把更新后的数据写回到对应磁盘的sector中。这层缓存则是块缓存Buffer Cache。
- 一个页缓存(page cache)包含多个块缓存(buffer cache),页缓存中的存储的数据实际上是由多个块缓存来负责存储。
- File在地址空间上,以4K(page size)为单位进行切分,每一个4k都可能对应到一个page上(只有被缓存的部分,才会对应到page上,没有缓存的部分,则不会对应),而这个4k的page,就是这个文件的一个Page Cache。而对于落磁盘的一个文件而言,最终,这个4k的page cache,还需要映射到一组磁盘block对应的buffer cache上,假设block为1k,那么每个page cache将对应一组(4个)buffer cache,而每一个buffer cache,则有一个对应的buffer cache与device block映射关系的描述符:buffer_head,这个描述符记录了这个buffer cache对应的block在磁盘上的具体位置。
- 页缓存的大小是在一直动态变化的。当系统内存充足时,页缓存会一直增大;当系统free内存不足时,这时如果有进程申请内存,操作系统会从page cache中回收内存页进行分配,如果page cache也已不足,那么系统会将当期驻留在内存中的数据置换到事先配置在磁盘上的swap空间中,然后空出来的这部分内存就可以用来分配了。这就是swap交换。
- 出现swap交换时,数据被置换到swap空间后(swap out),该进程使用的内存量下降,但这并不表示该进程释放了内存,当它需要时,这部分数据又会被换入到内存中(swap in)。另外, swap交换往往会带来磁盘IO的大量消耗,严重影响到系统正常的磁盘io。出现大量的swap交换说明系统已经快要不行了,需要重点关注。
虚拟内存
- 通过引入虚拟内存,每个进程都有自己独立的虚拟地址空间,这个空间理论上可以无限大,因为它并不要钱。一个进程同一时刻不可能所有变量数据都会访问到,只需要在访问某部分数据时,把这一块虚拟内存映射到物理内存,其他没有实际访问过的虚拟地址空间并不会占用到物理内存,这样对物理内存的消耗就大大减少了 。
- 系统内核为每个进程都维护了一份从虚拟内存到物理内存的映射表,称为页表。
- 页表根据虚拟地址,查找出所映射的物理页位置和数据在物理页中的偏移量,便得到了实际需要访问的物理地址。
- 虚拟内存的大小 = 物理内存大小 + 交换空间的大小。top命令中:VIRT=SWAP+RES:
- 驻留内存(RSS):指虚拟内存中实际映射到物理内存的那部分,也就是进程实际占用的物理内存大小。判断一个进程使用内存的大小,我们主要是看这个进程的驻留内存大小。
共享内存
- 进程在运行过程中,会加载许多操作系统的动态库,比如 libc.so、libld.so等。这些库对于每个进程而言都是公用的,它们在内存中实际只会加载一份,这部分称为共享内存。
- 注意,进程占用的共享内存也是计算到驻留内存中的。