free命令可以显示当前系统未使用的和已使用的内存数目,还可以显示被内核使用的内存缓冲区。
重新认识free命令参考文档:
- javascript:void(0)
- https://tldp.org/LDP/sag/html/buffer-cache.html
- https://access.redhat.com/solutions/406773
由于我们执行free命令时都是一次性的,所以首先需要打破一个可能存在的误区——那就是free命令的输出,其实是非常动态的,在系统负载较高的情况下,free命令的输出每时每刻都在发生变化;即使是系统负载并不高的情况下,free命令的输出也是在发生变化的,我们执行
free -k -s 1
# -s用于指明输出的间隔秒数
所以,当计算结果存在误差时不必过多在意,计算结果近似即可。
通常情况下, 当我们执行
free -h
命令时,输出结果应该是这个样子的:
[root@demo ~]# free -h
total used free shared buff/cache available
Mem: 7.6G 481M 6.6G 12M 514M 6.8G
Swap: 3.9G 0B 3.9G
如果用的是CentOS/RHEL 6或者更早之前的系统,你的输出中应该还包含了一行专门的
buffers/cached
,他们等同于上面的
buff/cache
。
像是这样:
[root@tencent64 ~]# free
total used free shared buffers cached
Mem: 132256952 72571772 59685180 0 1762632 53034704
-/+ buffers/cache: 17774436 114482516
Swap: 2101192 508 2100684
下面我们以CentOS 7为例,来介绍free命令输出中每个字段的含义
- total:可用的内存总量,Total installed memory (MemTotal and SwapTotal in /proc/meminfo)
- used: 已安装的总物理内存-free-bufers-cache计算出来的结果。(calculated as total - free - buffers - cache)
- free:系统未使用的物理内存总量,Unused memory (MemFree and SwapFree in /proc/meminfo),这部分是真正未被分配出去使用的物理内存。
- shared:共享内存,由于是多个进程间共享使用的,所以,类似于tmpfs这种RAM disk所使用的内存也会被算进去, (Shmem in /proc/meminfo, available on kernels 2.6.32, displayed as zero if not available)
- buff/cache:buffer是
文件中输出的/proc/meminfo
字段;cache是Buffers
/proc/meminfo
+Cached
字段,通常情况下,共享内存也会算进去这个字段中。Slab
- Buffers:buffer cache区域,(Buffers are the disk block representation of the data that is stored under the page caches. Buffers contains the metadata of the files/data which resides under the page cache.)。是内存中关于真实物理设备上的数据的读缓存和写缓冲,是通常涉及到物理设备的驱动的操作。当内核对Cached内存部分的数据操作需要涉及到操作物理设备时,内核将首先检查Buffers内存区域中有没有这个数据的元数据(实际的块地址)
- Cached:
- 缓存是存储数据的内存部分,以便将来可以更快地提供该数据的请求。内核利用此内存缓存磁盘数据并提高 i/o 性能。
- Linux 内核的构建方式是尽可能多地使用 RAM 来缓存来自本地和远程文件系统和磁盘的信息。随着在系统上执行各种读取和写入的时间,内核会尝试将存储在内存中的数据保留在系统上运行的各种进程或在不久的将来将使用的相关进程的数据。当进程停止/退出时,不会回收缓存,但是当其他进程需要比可用内存更多的内存时,内核将运行启发式方法,通过将已缓存的内存分配给新进程来自行回收内存。
- 当请求任何类型的文件/数据时,内核将查找用户正在操作的文件部分的副本,如果不存在此类副本,它将分配一个新的缓存内存页,并填充从磁盘中读出的适当内容。
- 存储在缓存中的数据可能是之前计算过的值,或者存储在磁盘其他位置的原始值的重复值。请求某些数据时,首先检查缓存以查看它是否包含该数据。从缓存中检索数据的速度比从源源更快地检索数据。
- 共享内存段也被视为缓存,尽管它们不表示磁盘上的任何数据
- SLAB内存:Linux默认使用4kb大小的页(page)来管理内存,这样,如果有进程需要使用小于4k的内存来管理对象,例如只有几个字节,而且通常情况下这种小对象的创建和销毁都会非常频繁,这种场景下,内核分配一个页给这样的进程就会显得特别浪费。这就引入了SLAB内存,slab分配机制可以在一个页内细分成更小的存储器单元,将这些细小的内存区域提供给进程使用。此部分用于标识当前系统上已分配出去的SLAB内存。
- sreclaimable:可回收的slab内存
- sunreclaim:不可回收的slab内存
- 总结:Buffers是对物理设备上的数据的缓存/缓冲,而 Cached是文件数据的缓存/缓冲,它们既会用在读请求中,也会用在写请求,根据操作对象的不同,内核将使用Buffers或者Cached来缓存/缓冲数据
- available:在不考虑SWAP的情况下,估算有多少内存可用于启动新的应用程序。和cache和free字段不同,这个值同时考虑到了Page Cache,也需要注意并不是所有Page Cache都是可以被回收的,例如,在使用的过程中,内核自身的数据结构Slabs内存就是无法被回收的。这个值是最接近于当前系统真实可用的内存的值,真实的计算方式是
。MemAvailable in /proc/meminfofree字段+buffer/cache字段中可以被drop_caches回收的值
- 无法被回收的内存有哪些?
- 挂载的tmpfs上存储的文件,这些文件是直接存储于内存中的;
- 进程使用的共享内存,也是存储于cache中的,这一段不能被回收,可以手动执行
进行释放ipcrm -m ID
- mmap函数创建出来的内存,此函数通常用于将文件映射至内存中,但是Linux注明的内存注册函数malloc函数在申请大段内存时,使用的也是这个函数,使用mmap函数进行共享内存的申请时,被申请的共享内存也是无法释放的。
- 实际上,available字段的近似计算方式是free+(buffer&cache/2),这是free的作者根据经验的估算,判断buffer和cache里面大约有50%是不可回收的内存。
- available字段的计算代码:
-
available = i.freeram - wmark_low; + + /* + * Not all the page cache can be freed, otherwise the system will + * start swapping. Assume at least half of the page cache, or the + * low watermark worth of cache, needs to stay. + */ + pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE]; + pagecache -= min(pagecache / 2, wmark_low); + available += pagecache; + + /* + * Part of the reclaimable swap consists of items that are in use, + * and cannot be freed. Cap this estimate at the low watermark. + */ + available += global_page_state(NR_SLAB_RECLAIMABLE) - + min(global_page_state(NR_SLAB_RECLAIMABLE) / 2, wmark_low); + + if (available < 0) + available = 0;
- 无法被回收的内存有哪些?
下面用实际操作来验证Buffers和Cached的区别
写
# cached
vmstat 1
dd if=/dev/urandom of=/tmp/file bs=1M count=1024
# buffers
vmstat 1
dd if=/dev/urandom of=/dev/sdb1 bs=1M count=1024
读
# cached
vmstat 1
dd if=/tmp/file of=/dev/null bs=1M count=1024
# buffers
vmstat 1
dd if=/dev/sdc of=/dev/null bs=1M count=1024
下面有三个例子用于测试什么情况下,Buffers/Cached区域的内存将出现无法回收的内存
用于测试tmpfs的命令
mkdir /var/tmp/tmpfs
mount -t tmpfs -o size=4G tmpfsTest /var/tmp/tmpfs/
cd /var/tmp/tmpfs/
dd if=/dev/zero of=/var/tmp/tmpfs/tmpfsTestFile bs=1M count=2048
# 之后再Drop Caches
# echo 3 > /proc/sys/vm/drop_caches
# 将可以看到buff/cache区域的内存并没有被回收
用于共享内存测试的C代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#define MEMSIZE 2048*1024*1023
int main()
{
int shmid;
char *ptr;
pid_t pid;
struct shmid_ds buf;
int ret;
shmid = shmget(IPC_PRIVATE, MEMSIZE, 0600);
if (shmid<0) {
perror("shmget()");
exit(1);
}
ret = shmctl(shmid, IPC_STAT, &buf);
if (ret < 0) {
perror("shmctl()");
exit(1);
}
printf("shmid: %d\n", shmid);
printf("shmsize: %d\n", buf.shm_segsz);
buf.shm_segsz *= 2;
ret = shmctl(shmid, IPC_SET, &buf);
if (ret < 0) {
perror("shmctl()");
exit(1);
}
ret = shmctl(shmid, IPC_SET, &buf);
if (ret < 0) {
perror("shmctl()");
exit(1);
}
printf("shmid: %d\n", shmid);
printf("shmsize: %d\n", buf.shm_segsz);
pid = fork();
if (pid<0) {
perror("fork()");
exit(1);
}
if (pid==0) {
ptr = shmat(shmid, NULL, 0);
if (ptr==(void*)-1) {
perror("shmat()");
exit(1);
}
bzero(ptr, MEMSIZE);
strcpy(ptr, "Hello!");
exit(0); } else { wait(NULL); ptr = shmat(shmid, NULL, 0); if (ptr==(void*)-1) { perror("shmat()"); exit(1); } puts(ptr); exit(0); }}
测试
gcc shmem.c -o shmem
# 之后再Drop Caches
# echo 3 > /proc/sys/vm/drop_caches
# 将可以看到buff/cache区域的内存并没有被回收
ipcs -m
# 查看共享内存的注册和使用情况,此输出中,重点关注shmid和字节数
ipcrm -m shmid
# 手动释放共享内存
用于测试mmap的C代码
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define MEMSIZE 1024*1024*1023*2
#define MPFILE "./mmapfile"
int main()
{
void *ptr;
int fd;
fd = open(MPFILE, O_RDWR);
if (fd < 0) {
perror("open()");
exit(1);
}
ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, fd, 0);
if (ptr == NULL) {
perror("malloc()");
exit(1);
}
printf("%p\n", ptr);
bzero(ptr, MEMSIZE);
sleep(30);
munmap(ptr, MEMSIZE);
close(fd);
exit(1);
}
dd if=/dev/zero of=./mmapfile bs=1M count=2048
gcc mmap.c -o mmap
./mmap &
# 之后再Drop Caches
# echo 3 > /proc/sys/vm/drop_caches
# 将可以看到buff/cache区域的内存并没有被回收
总结
1、Buffers是对物理设备上的数据的缓存/缓冲,而 Cached是文件数据的缓存/缓冲,它们既会用在读请求中,也会用在写请求,根据操作对象的不同,内核将使用Buffers或者Cached来缓存/缓冲数据
2、buff/cache中的内存并不是都是可以被释放的,有至少三种内存无法在系统内存不够用时被回收:
- tmpfs等ram disk申请的内存
- 共享内存
- mmap申请的、未被释放的共享内存
3、最合理、通用的内存空闲率计算方式应该是free+(buff&cache/2),近似于free命令输出中的available字段;最合理、通用的已用内存方式的计算应该是total-free-(buff&cache/2),近似于total-available,应当根据业务的不同,来计算内存的空闲率,一些严重依赖于Cached区域内存的业务,不应该将buff/cache也算成是可以被使用的内存,例如ceph metadata server和Kafka这种严重依赖于Cached区域内存的业务,因为这些业务所占用的Cached内存没办法被回收,或者说一回收肯定会出问题。
free+(pagecache/2 + sreclaimable/2)
参考链接:https://www.bilibili.com/video/BV1q54y1W7K5?from=search&seid=3224528125160665682
==================================================================