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
==================================================================