今天在跑一个python排序程序的时候,想通过top命令检查程序消耗内存的变化。很奇怪,检测到的内存使用比预期的要高不少。刚好借这个机会整理下Linux中内存管理的一些知识。
文章目录
free命令
通过free
命令可以查看当前系统的内存使用情况,参数-h
可以将单位自动转换的比较友好
[root@testmachine tools]# free -h
total used free shared buff/cache available
Mem: 3.7G 1.8G 163M 9.1M 1.8G 1.6G
Swap: 2.0G 925M 1.1G
先看第一行Mem
部分的内容
- total - 物理内存的总和
- used - 已经被应用程序使用的内存大小
- free - 完全空闲的内存大小
- shared - 被共享的内存
- buff/cache - 被当作缓存使用的内存大小
- available - 可以被新的应用程序使用的内存大小
这里可能有朋友会有疑问,你电脑3.7G的内存怎么只剩下了163M,是不是系统出了问题?或者为什么只有163M内存可用,但新的程序却又可以使用1.6G的内存?这些疑问我们下面都会解答。
再来看第二行Swap,交换区
- total - 可被用作swap的硬盘总大小
- used - 被使用的swap大小
- free - 可被使用的swap大小
至于什么是交换区,有什么用,后面我们也会单独说。
内存映射与页
先来说说内存使用的基本逻辑。
每个进程都有自己的独立寻址空间,也就是说某进程创建的变量不会被另一个进程访问到。进程能寻址的地址叫做虚拟内存,针对32位寻址的机器,每个进程有2^32
个寻址地址,也就是4GB,而64位寻址的机器,理论上有2^64
个寻址地址。但是受限于主板和地址线个数,目前内存条基本都是16GB的(2020年7月),物理内存条对应的是物理内存。
那么问题就来了,虚拟内存和物理内存之间大小明显不匹配,物理内存似乎完全不够用,这是怎么回事?
解答这个问题前,先来聊一聊页(Page)。
计算机会对虚拟内存和物理内存按照最小单位4KB进行处理,虚拟内存中的4KB称为页(Page),物理内存中的4KB称为页帧(Page Frame),页和页帧之间的对应关系称为内存映射。所有的进程共享同一物理内存,每个进程也只会把目前需要的虚拟内存地址映射到物理内存中。
进程去页中查询所需要的数据,而真正的数据都在页帧中,所以还需要一个页表(Page Table)去管理这种对应关系以便进程去查找。页表的每一条记录分为两部分,第一部分记录此页是否在物理内存中,第二部分记录物理内存页帧的地址(如果存在的话)。
当进程想要访问某个虚拟地址,但是没有在页表中查到物理内存的地址,说明数据还没有读到内存中,这叫做缺页异常(Page Fault)。此时进程会将数据从硬盘读到物理内存中,并更新页表。假如从硬盘读入内存的时候,内存中的页帧都被用了,就会找一个使用较少的页帧进行覆盖。
所以就可以回答上面的问题了。虽然每个进程有自己独立的虚拟内存地址,但是只是把需要用到的部分映射到物理内存,有了新的需求再去映射新的。所以虽然所有进程都共享物理内存,也会彼此独立。
那么内存中使用完毕的页帧会被马上收回吗?答案是并不会。这也是为什么好像机器上没有运行啥程序但是还是显示free
的内存很少的原因,因为大量的内存都是之前使用过,但是并没有释放的部分,这就是buffer
和cache
。
buffer和cache
先来看下面这个现象。我这里有一个1.1GB大小的文件,同样是读取整个文件,第一次读取花了1.3秒,而以后再读取只需要0.2秒
[root@testmachine linkedlist]# ll -h bigfile.txt
-rw-rw-r-- 1 fuhx fuhx 1.1G Jul 17 17:37 bigfile.txt
[root@testmachine linkedlist]# free -h
total used free shared buff/cache available
Mem: 3.7G 1.7G 1.8G 17M 227M 1.7G
Swap: 2.0G 922M 1.1G
[root@testmachine linkedlist]# time cat bigfile.txt > /dev/null
real 0m1.317s
user 0m0.006s
sys 0m1.306s
[root@testmachine linkedlist]# time cat bigfile.txt > /dev/null
real 0m0.236s
user 0m0.006s
sys 0m0.229s
[root@testmachine linkedlist]# time cat bigfile.txt > /dev/null
real 0m0.211s
user 0m0.013s
sys 0m0.198s
[root@testmachine linkedlist]# free -h
total used free shared buff/cache available
Mem: 3.7G 1.7G 729M 17M 1.3G 1.6G
Swap: 2.0G 922M 1.1G
同样值得注意的是,读取文件前后,free
部分的内存下降了约1GB,而buff/cache
部分的内存上升了约1GB。
这说明整个文件都被缓存在了内存中,除了第一次调用需要从硬盘读到内存,以后每次都是直接从内存读取,所以会快很多
buffer和cache一个是缓冲区一个是缓存,不过这里都可以理解成内存和硬盘之间交互的一个中间层。
page cache
针对用页来管理的内存,其缓存被称为page cache。其主要是在进程对文件进行读写操作时发挥作用。
用户读写与page cache
读文件时,用户发起read操作,进程查找页表,如果有映射到页帧,则直接返回页帧中返回的内容,如果没有匹配的页帧,则触发缺页异常,从硬盘读取数据到页帧进行缓存,read操作完成。
写文件时,用户发起write操作,进程查找页表,如果有映射到页帧,直接写到页帧中,此时的页被称为脏页(Dirty Page),如果没有匹配的页帧,则创建页帧写入数据。用户write操作完成。此后操作系统有两种方式将脏页回写到硬盘,用户手动调用fsync()
或者由pdflush
进程定时将脏页写回。
不管是读还是写,有对应的cache称为命中,命中率是判断某进程缓存好换的关键指标。
这也就回答了上面的疑问,电脑3.7G的内存怎么只剩下了163M,是不是系统出了问题?并不是电脑出了问题,而是Linux的内存管理机制就是这样,尽可能地对硬盘数据进行缓存,以加快读写速率。这一点跟Windows不一样。
当有新的程序要申请内存时,这些被缓存占用的内存会被释放,这就是为什么available
的内存会将缓存也算进去。
当然,虽然不建议,但是如果想手动清除cache也是可以的。
手动清理cache
通过修改内核参数可以达到删除cache的目的
[root@testmachine tools]# free -h
total used free shared buff/cache available
Mem: 3.7G 1.8G 163M 9.1M 1.8G 1.6G
Swap: 2.0G 925M 1.1G
[root@testmachine tools]# echo 3 > /proc/sys/vm/drop_caches
[root@testmachine tools]# free -h
total used free shared buff/cache available
Mem: 3.7G 1.7G 1.7G 9.1M 221M 1.7G
Swap: 2.0G 925M 1.1G
Linux中一切皆文件,内核的各种设置也以文件形式提供操作API,这就是
/proc/sys/
目录。临时的修改只需要将值echo到文件即可,因为是直接作用于内存所以马上生效,但是机器重启就会消失。永久生效需要在/etc/sysctl.conf
或者是/etc/sysctl.d/xxx.conf
中将目录换成点号进行赋值。例如echo 1 > proc/sys/net/ipv4/ip_forward
就写成net.ipv4.ip_forward=1
然后跑命令sysctl -p
从文件生效
关于内核参数调优,可以参考这一篇博客,和内存相关的都在
/proc/sys/vm/
目录下
首先,这种清理也并不是没有成本的。前面说过用户执行写操作会产生脏页,如果存在脏页,系统要先写回到硬盘才能回收内存。所以清除缓存通常伴随着高I/O。
同时可以发现cache并不会完全清除,这是因为有些cache不能被系统回收,例如tmpfs
,进程间的通讯用的共享内存等等,更多细节可以参考Linux 内存中的Cache,真的能被回收么?。
再次强调,Linux的缓存是不需要手动去清理的。
内存使用增多排查步骤
如果发现内存在一点点被蚕食,可以按照这篇文章推荐的步骤来排查
- 通过 free,发现大部分内存都被缓存占用后
- 可以使用 vmstat 或者 sar观察一下缓存的变化趋势,确认缓存的使用是否还在继续增大。
- 如果继续增大,则说明导致缓存升高的进程还在运行,那你就能用缓存 / 缓冲区分析工具(比如 cachetop、slabtop 等),分析这些缓存到底被哪里占用。
- 排除缓存 / 缓冲区后,你可以继续用 pidstat 或者 top,定位占用内存最多的进程。
- 找出进程后,再通过进程内存空间工具(比如 pmap),分析进程地址空间中内存的使用情况就可以了。
- 通过 vmstat 或者 sar 发现内存在不断增长后,可以分析中是否存在内存泄漏的问题。比如你可以使用内存分配分析工具 memleak ,检查是否存在内存泄漏。如果存在内存泄漏问题,memleak 会为你输出内存泄漏的进程以及调用堆栈
内存泄漏:memory leak,内存使用完毕没有回收,导致该区域永远无法被后面的进程使用的现象
swap
下面来说说swap,也就是交换区。
所谓的swap,就是将一部分硬盘当作内存来使用。内存中不常用的一部分会被交换到swap,下次再使用的时候再交换回内存,分别叫做换出和换入。
既可以在分区的时候就设定一块盘做为swap,例如我电脑这样
[root@testmachine shm]# fdisk -l
Disk /dev/sda: 53.7 GB, 53687091200 bytes, 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000c328c
Device Boot Start End Blocks Id System
/dev/sda1 * 2048 2099199 1048576 83 Linux
/dev/sda2 2099200 41943039 19921920 8e Linux LVM
/dev/sda3 41943040 104857599 31457280 83 Linux
Disk /dev/mapper/centos-root: 49.4 GB, 49387929600 bytes, 96460800 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/mapper/centos-swap: 2147 MB, 2147483648 bytes, 4194304 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
或者像下面这样创建一个文件做为swap。
文件作为swap分区
1.创建要作为swap分区的文件:增加1GB大小的交换分区,则命令写法如下,其中的count等于想要的块的数量(bs*count=文件大小)。
dd if=/dev/zero of=/root/swapfile bs=1M count=1024
2.格式化为交换分区文件:
mkswap /root/swapfile #建立swap的文件系统
3.启用交换分区文件:
swapon /root/swapfile #启用swap文件
4.使系统开机时自启用,在文件/etc/fstab中添加一行:
/root/swapfile swap swap defaults 0 0
swappiness
有一个内核参数如下
[root@testmachine shm]# cat /proc/sys/vm/swappiness
30
这是一个介于0-100的配置项,用来表示系统进行内存回收时候的偏好。数值越大,在内存回收时越偏好使用swap,也就是将一些不常用的页换出;数值约小,在内存回收时越拒绝使用swap,也就是更倾向将缓存直接释放或者脏页写回硬盘再释放。
通常为了提升性能,建议将这个值设置为0。
内存管理优化
在这篇文章中还推荐了很多内存优化的工具,像cachetop,vmstat等,以及一些优化思路。做为补充内容放在这里了。
我是T型人小付,一位坚持终身学习的互联网从业者。喜欢我的博客欢迎在csdn上关注我,如果有问题欢迎在底下的评论区交流,谢谢。