Linux内存工作原理 内存分配与回收 malloc() 是 C 标准库提供的内存分配函数,对应到系统调用上,有两种实现方式,即 brk() 和 mmap()。
对小块内存(小于 128K),C 标准库使用 brk() 来分配,也就是通过移动堆顶的位置来分配内存。这些内存释放后并不会立刻归还系统,而是被缓存起来,这样就可以重复使用。
对大块内存(大于 128K),则使用内存映射 mmap() 来分配,也就是在文件映射找一块空闲内存分配出去。
各自的优缺点 :
brk() 方式的缓存,可以减少缺页异常的发生,提高内存访问效率;不过,由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片 mmap() 方式分配的内存,会在释放时直接归还系统,所以每次 mmap 都会发生缺页异常。在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大 整体来说,Linux 使用伙伴系统 来管理内存分配。前面我们提到过,这些内存在 MMU 中以页为单位进行管理,伙伴系统也一样,以页为单位来管理内存,并且会通过相邻页的合并,减少内存碎片化(比如 brk 方法造成的内存碎片)。
在用户空间,malloc 通过 brk() 分配的内存,在释放时并不立即归还系统,而是缓存起来重复利用。在内核空间,Linux 通过 slab 分配器来管理小内存,可以把 slab 堪称构建在伙伴系统上的一个缓存 ,主要作用就是分配并释放内核中的小对象。
系统也不会任由某个进程用完所有内存。在发现内存紧张时,系统会通过一系列机制来回收内存:
回收内存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面 回收不常访问的内存,把不常用的内存交换分区直接写到磁盘中(会用到交换分区) 杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程 OOM 是内核的一种保护机制。它监控进程的内存使用情况,并且使用 oom_score 为每个进程的内存使用情况进行评分:
进程消耗的内存越大,oom_score 就越大 进程运行占用的 CPU 越多,oom_score 就越小 可以手动设置进程的 oom_adj 来调整 oom_score。oom_adj 的范围是 [-17, 15],数值越大,进程越容易被杀死;反之,越不容易被杀死。
如何查看内存使用情况 1、free 命令
1 2 3 4 $ free total used free shared buff/cache available Mem: 262695500 106731876 141173212 4257008 14790412 149168720 Swap: 0 0 0
total:总内存 used:已使用内存,包括共享内存 free:空闲内存 shared:共享内存 buff/cache:缓存内存,包括缓冲区和缓存 available:可用内存,包括空闲内存和缓存内存 注意 :available 不仅包含未使用内存,还包括了可回收的缓存,所以一般会比未使用内存更大。不过,并不是所有缓存都可以回收,因为有些缓存可能正在使用中。
2、top 命令
可以查看每个进程的内存使用情况
1 2 3 4 5 6 7 8 9 10 11 12 13 top - 19:28:02 up 201 days, 2:48, 2 users, load average: 134.36, 136.81, 111.38 Tasks: 1284 total, 6 running, 1278 sleeping, 0 stopped, 0 zombie %Cpu(s): 87.2 us, 8.9 sy, 0.0 ni, 0.9 id, 0.0 wa, 1.8 hi, 1.1 si, 0.0 st MiB Mem : 256538.6 total, 137892.3 free, 104201.3 used, 14445.0 buff/cache MiB Swap: 0.0 total, 0.0 free, 0.0 used. 145701.9 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 607070 root 20 0 266.3g 82.5g 79228 S 4861 33.0 40025:32 tabletserver_ma 835494 root 20 0 8557368 207048 10872 S 121.7 0.1 357:04.40 java 842048 1001 20 0 32.8g 31168 14888 S 113.0 0.0 0:03.41 java 840498 root 20 0 727932 31336 7304 R 91.3 0.0 2164:46 node_exporter 653917 root 20 0 12.3g 997944 46948 S 78.3 0.4 15877:42 kubelet...
VIRT:虚拟内存,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内 RES:实际内存,也就是进程实际使用的物理内存 大小,但不包括 Swap 和共享内存 SHR:共享内存,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等 %MEM:进程占用的物理内存占系统总内存的百分比 注意 :
虚拟内存通常并不会全部分配物理内存。从上面的输出,你可以发现每个进程的虚拟内存都大于实际内存,这是因为虚拟内存是进程申请的内存,即使进程没有真正分配物理内存,也会计算在内。 共享内存 SHR 并不一定是共享的,比方说,程序的代码段、非共享的动态内存链接库 ,也都在 SHR 里。SHR 也包括了进程间真正共享的内存 。所以在计算多个进程的内存使用时,不要把所有进程的 SHR 直接相加得出结果 内存的 Buffer 和 Cache free 的数据来源 man free 查看
从手册看到:
Buffers:Memory used by kernel buffers (Buffers in /proc/meminfo) Cache:Memory used by the page cache and slabs (Cached and Slab in /proc/meminfo) proc 文件系统 man proc 查看
1 2 3 4 5 6 7 8 9 10 11 Buffers %lu Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20 MB or so). Cached %lu In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached. ... SReclaimable %lu (since Linux 2.6 .19 ) Part of Slab, that might be reclaimed, such as caches. SUnreclaim %lu (since Linux 2.6 .19 ) Part of Slab, that cannot be reclaimed on memory pressure.
通过文档可以看到:
Buffers 是对原始磁盘的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写入合并成单次大的写等等。 Cached 是从单磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。 Slab 代表内核数据结构缓存,包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录。 案例测试 场景一:磁盘和文件写 运行 vmstat 命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 $ vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 1218088 16790772 60 8758252 0 0 24 97 0 0 12 6 82 0 0 4 0 1218088 16779960 60 8762060 0 0 0 159 15274 28820 16 9 75 0 0 2 0 1218088 16779968 60 8761840 0 0 0 154 13467 26984 9 4 87 0 0 0 0 1218088 16778620 60 8762068 0 0 0 212 13980 26715 6 5 89 0 0 6 0 1218088 16773376 60 8762136 0 0 0 170 12838 25315 4 3 93 0 0 2 0 1218088 16780544 60 8762164 0 0 0 343 13195 25313 5 4 91 0 0 0 0 1218088 16783260 60 8762392 0 0 0 116 18099 33200 13 10 78 0 0 1 0 1218088 16782896 60 8762400 0 0 0 40 10645 21760 2 2 96 0 0 0 0 1218088 16780616 60 8762412 0 0 0 97 12603 24496 3 3 94 0 0 1 0 1218088 16780708 60 8762436 0 0 0 90 14724 27729 9 4 87 0 0 1 0 1218088 16779376 60 8762472 0 0 0 116 12702 25101 3 3 93 0 0 0 0 1218088 16775968 60 8765976 0 0 0 133 16290 29873 19 10 71 0 0 0 0 1218088 16783224 60 8765996 0 0 0 89 13359 25299 4 3 93 0 0 0 0 1218088 16782604 60 8766016 0 0 0 73 10976 22399 2 2 96 0 0 8 0 1218088 16775272 60 8766032 0 0 0 122 13109 26159 4 2 94 0 0
buff 和 cache 就是我们前面看到的 Buffers 和 Cache,单位是 KB bi 和 bo 则分别表示块设备读取和写入的大小,单位为 块/秒。因为 Linux 中块的大小是 1KB,所以这个单位也就等价于 KB/s 在另一中端执行 dd 命令通过读取随机设备,生成一个 500MB 大小的文件
1 dd if =/dev/urandom of =/mnt/disk1/log bs =1M count =500
继续观察 buff 和 cache 的变化如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 6 0 1218088 16808024 60 8706284 0 0 0 135 17194 30511 12 5 82 0 0 2 0 1218088 16812680 60 8706316 0 0 0 108 14214 27386 3 2 94 0 0 0 0 1218088 16822344 60 8706268 0 0 0 82 12608 25281 2 2 96 0 0 0 0 1218088 16828356 60 8706284 0 0 0 65 16971 31578 6 5 89 0 0 ... 1 2 1218088 16438876 60 9110220 0 0 0 264629 19007 27977 5 20 73 1 0 8 1 1218088 16324916 60 9227456 0 0 0 22966 20049 31235 13 21 61 5 0 2 0 1218088 16329904 60 9227244 0 0 0 327 11770 24005 4 3 87 7 0 0 0 1218088 16329340 60 9227248 0 0 0 91 11558 22878 3 2 95 0 0 5 0 1218088 16329532 60 9227264 0 0 0 88 14350 28661 10 5 85 0 0 1 0 1218088 16325480 60 9227056 0 0 0 162 16735 30309 11 8 81 0 0 2 1 1218088 16325424 60 9230360 0 0 0 225680 15589 31332 15 10 72 2 1 1 0 1218088 16325160 60 9231060 0 0 0 124 14815 26517 10 6 79 5 0
可以看到:
在 dd 命令运行时,Cache 在不停地增长,而 Buffer 基本保持不变 在 dd 命令结束后,Cache 不再增长,但是块设备写还会持续一段时间,并且,多次 1/0 写的结果加起来,才是 dd 要写的 500M 的数据 下面的命令对环境要求很高,需要你的系统配置多块磁盘,并且磁盘分区 /dev/sdb1 还要处于未使用状态。如果你只有一块磁盘,千万不要尝试,否则将会对你的磁盘分区造成损坏 。
如果你的系统符合标准,就可以继续在第二个终端中,运行下面的命令。清理缓存后,向磁盘分区 /dev/sdb1 写入 2GB 的随机数据:
1 2 3 # 首先清理缓存 $ echo 3 > /proc/sys/vm/drop_caches# 然后运行 dd 命令向磁盘分区 /dev/sdb1 写入 2G 数据$ dd if =/dev/urandom of=/dev/sdb1 bs=1M count=2048
然后,再回到终端一,观察内存和 I/O 的变化情况:
1 2 3 4 5 6 7 8 9 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 0 7584780 153592 97436 0 0 684 0 31 423 1 48 50 2 0 1 0 0 7418580 315384 101668 0 0 0 0 32 144 0 50 50 0 0 1 0 0 7253664 475844 106208 0 0 0 0 20 137 0 50 50 0 0 1 0 0 7093352 631800 110520 0 0 0 0 23 223 0 50 50 0 0 1 1 0 6930056 790520 114980 0 0 0 12804 23 168 0 50 42 9 0 1 0 0 6757204 949240 119396 0 0 0 183804 24 191 0 53 26 21 0 1 1 0 6591516 1107960 123840 0 0 0 77316 22 232 0 52 16 33 0
从这里你会看到,虽然同是写数据,写磁盘跟写文件的现象还是不同的。写磁盘时(也就是 bo 大于 0 时),Buffer 和 Cache 都在增长,但显然 Buffer 的增长快得多。
场景二:磁盘和文件读 运行文件读的命令如下
1 2 # 文件读 $ dd if =/mnt/disk1/log of=/dev/null
1 2 3 4 5 6 7 8 9 10 11 $ echo 3 > /proc/sys/vm/drop_caches $ mstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 1 0 1218088 22631652 0 3037032 0 0 792 225 15443 29060 17 8 75 0 0 1 0 1218088 22629780 0 3037096 0 0 48 33 13210 25184 3 3 94 0 0 8 0 1218088 22474456 0 3192744 0 0 155593 379 11851 21896 4 6 89 0 0 5 0 1218088 22184464 0 3471364 0 0 282644 181 13324 22371 5 12 83 0 0 0 0 1218088 22119672 0 3549248 0 0 73920 82 14720 26164 7 8 84 0 0 0 0 1218088 22118224 0 3549468 0 0 136 29 17030 32835 13 9 78 0 0 2 0 1218088 22116736 0 3549488 0 0 80 327 11854 23476 4 2 93 0 0
运行磁盘读的命令如下
1 2 $ dd if =/dev/vdc of =/dev/null bs =1M count =1024
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 $ dvmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 6 0 1218088 21827684 0 3716568 0 0 24 97 0 0 12 6 82 0 0 4 0 1218088 21816680 0 3721036 0 0 0 117 16130 30199 16 11 73 0 0 0 0 1218088 21819240 0 3720212 0 0 9 195 14200 26901 6 4 89 0 0 1 0 1218088 21818228 0 3720252 0 0 4 123 11836 23517 3 2 95 0 0 1 0 1218088 21802832 14336 3720200 0 0 17369 248 10953 21760 3 2 91 4 0 2 1 1218088 21740512 75776 3720308 0 0 58416 77 14897 27732 4 5 79 11 0 3 1 1218088 21696460 133120 3720692 0 0 57344 76 20717 40621 14 10 66 9 0 10 0 1218088 21639256 190464 3720328 0 0 57372 349 18512 29321 11 5 75 10 0 2 1 1218088 21600056 247808 3720308 0 0 57344 72 17332 28977 10 5 74 10 0 8 1 1218088 21539120 296960 3720340 0 0 49152 688 22618 37060 16 8 66 9 0 0 1 1218088 21473532 354304 3720184 0 0 57348 109 18331 30615 9 5 74 11 0 1 1 1218088 21414276 407552 3723624 0 0 53248 150 16057 29868 17 9 64 10 0 0 1 1218088 21356020 464896 3723924 0 0 57344 29 14159 25301 12 4 74 10 0 2 1 1218088 21302964 518144 3723736 0 0 53248 147 10678 21725 3 2 84 12 0 2 0 1218088 21278632 542720 3724092 0 0 24576 428 10163 21080 2 2 91 5 0 8 0 1218088 21269044 542720 3724072 0 0 0 73 15601 29313 7 5 88 0 0 23 0 1218088 21272568 542720 3724488 0 0 0 33 20061 36123 22 12 67 0 0
可以看到,读取文件时(也就是 bi 大于 0 时),Buffer 保持不变,而 Cache 则在不停增长;而读取磁盘时,Cache 保持不变,Buffer 不断增长。
结论 Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中。