本文主要分析 linux 系統(tǒng)內(nèi)存統(tǒng)計(jì)的一些指標(biāo)以及進(jìn)程角度內(nèi)存使用監(jiān)控的一些方法。
開始閱讀這篇文章前,請(qǐng)先簡(jiǎn)單閱讀下面的幾篇文章。
-
《進(jìn)程眼中的線性地址空間》
-
《線程眼中的線性地址空間》
-
《聊聊內(nèi)存管理》
想必這幾篇文章過后,基本概念就不需要再贅述了。所以下文直接就找一臺(tái) Intel x86_64 架構(gòu)下安裝了 64bit Linux 系統(tǒng)的服務(wù)器作為例進(jìn)行相關(guān)的實(shí)驗(yàn)和結(jié)果分析。Linux 的內(nèi)存管理從物理內(nèi)存管理到虛擬內(nèi)存管理涉及的概念和統(tǒng)計(jì)項(xiàng)實(shí)在太多,本文從實(shí)用和系統(tǒng)運(yùn)維的角度出發(fā),只列舉一些最實(shí)用的統(tǒng)計(jì)。
從 free 命令開始
上面的背景介紹文章把內(nèi)存相關(guān)的基礎(chǔ)概念講的差不多了,這里不再贅述。本文定位是內(nèi)存統(tǒng)計(jì),所以從最基礎(chǔ)的內(nèi)存統(tǒng)計(jì)的命令—free
命令開始。執(zhí)行free
命令,可以看到如下的輸出:
縱向是內(nèi)存和Swap
分區(qū),橫向是統(tǒng)計(jì)項(xiàng)。縱向的含義以及Swap
不需要解釋,我們看橫向的統(tǒng)計(jì)項(xiàng):
-
total — 系統(tǒng)總內(nèi)存(其實(shí)就是從
/proc/meminfo
獲取的) -
used — 已使用內(nèi)存
-
free — 未使用的內(nèi)存
-
shared — 共享內(nèi)存的大小,主要是
tmpfs
-
buff / cache —
buffers
和cache
使用的內(nèi)存之和 -
available — 可用內(nèi)存,可以簡(jiǎn)單理解為未使用的內(nèi)存和可釋放的內(nèi)存之和(buffer、cache 可以釋放大部分,所以這里近似等于 free + buffer / cache 的大小)
這臺(tái)機(jī)器的系統(tǒng)和內(nèi)核稍微新一點(diǎn),這個(gè)輸出可能和你看到的不一樣,早先的free
命令的輸出是這樣:
這里的shared
為0,因?yàn)檫@臺(tái)服務(wù)器沒用共享內(nèi)存。這里多解釋下-/+ buffer/cache
這行,字面意思就是used - buffers/cache
和used + buffers/cache
。前者指的是從應(yīng)用程序角度系統(tǒng)被用掉了多少內(nèi)存,后者指的是從應(yīng)用程序角度看系統(tǒng)還有多少內(nèi)存能用。聽起來很復(fù)雜,其實(shí)說白了就是因?yàn)?code>buffers和cached
可以被釋放出來,多幾個(gè)指標(biāo)看看系統(tǒng)還能用多少內(nèi)存而已。
下面用幾個(gè)公式來解釋這個(gè)輸出:
1 2 3 4 5 6 7 8 9 10 11 |
# 內(nèi)存總量 = 已使用內(nèi)存 + 空閑內(nèi)存 `total` = `used` + `free` # 系統(tǒng)被用掉的內(nèi)存 `-buffers/cache` = `used` - `buffers` - `cached` # 系統(tǒng)還能用的內(nèi)存 `+buffers/cache` = `free` + `buffers` + `cached` # 所以,其實(shí)還有下面的公式 `total` = `-buffers/cache` + `+buffers/cache` |
buffers/cached
不是100%都能釋放出來使用的,上面的“可用內(nèi)存”其實(shí)就是個(gè)近似值。最上面新版本系統(tǒng)的輸出中有一個(gè)available
項(xiàng)目表示可用內(nèi)存,值小于free + buff/cache
,內(nèi)核 3.14 之后支持該特性(雖然也不是絕對(duì)意義上的精確的可用內(nèi)存大小,囧)。
這里稍微多說一點(diǎn)buffers
和cached
。Linux 2.4.10 內(nèi)核之前,磁盤的緩存有兩種,即Buffer Cache
和Page Cache
。前者緩存管理磁盤文件系統(tǒng)時(shí)讀取的塊,后者存放訪問具體文件內(nèi)容時(shí)生成的頁。在 2.4.10 之后,Buffer Cache
這個(gè)概念就不存在了,這些數(shù)據(jù)被放在Page Cache
中(這種Page
被稱為Buffer Pages
)。
簡(jiǎn)而言之,現(xiàn)在磁盤的 cache 只有 Page Cache
一種,在Page Cache
中,有一種Page
叫Buffer Page
,這種Page
都與一個(gè)叫buffer_head
的數(shù)據(jù)結(jié)構(gòu)關(guān)聯(lián),這些頁也就在內(nèi)存統(tǒng)計(jì)中用buffers
這個(gè)指標(biāo)來單獨(dú)統(tǒng)計(jì)了。
/proc/meminfo 詳解
很多命令的內(nèi)存統(tǒng)計(jì)都是從/proc/meminfo
讀取的。鑒于/proc/meminfo
的 man 文檔(man proc
)寫的實(shí)在不夠清晰,很多條目居然還是To be documented
狀態(tài),所以這里逐一列舉出來常見的統(tǒng)計(jì)項(xiàng)解釋一下。
首先明確一點(diǎn),內(nèi)核目前并沒有絕對(duì)精確的統(tǒng)計(jì)所有的內(nèi)存使用量,比如alloc_pages
接口申請(qǐng)的內(nèi)存不一定被統(tǒng)計(jì)在內(nèi)(除非所有調(diào)用alloc_pages
的代碼主動(dòng)進(jìn)行統(tǒng)計(jì),如果某些不講究的驅(qū)動(dòng)程序沒有主動(dòng)統(tǒng)計(jì)的話統(tǒng)計(jì)值就肯定對(duì)不上了)。
先看這三項(xiàng)全局統(tǒng)計(jì):
-
MemTotal — 總的全局可用內(nèi)存大小(即物理
RAM
減去保留的以及內(nèi)核代碼占用的,系統(tǒng)啟動(dòng)后一般固定不變) -
MemFree — 總的全局未使用內(nèi)存大小
-
MemAvailable — 內(nèi)核估計(jì)出來的全局可用內(nèi)存大小,非精確值(
MemFree
不代表所有可用的內(nèi)存,Cache/Buffer
、Slab
均有部分可以臨時(shí)釋放的內(nèi)存要計(jì)算在內(nèi))
用戶進(jìn)程的內(nèi)存頁分為兩種:
-
與文件關(guān)聯(lián)的內(nèi)存頁(
File-backed Pages
), 比如程序文件、讀取文件時(shí)數(shù)據(jù)對(duì)應(yīng)的緩存頁 -
與文件無關(guān)的匿名內(nèi)存頁(
Anonymous Pages
),比如進(jìn)程的堆、棧等分配的內(nèi)存
所有Page Cache
里的頁面都是File-backed Pages
,File-backed Pages
在內(nèi)存不足的時(shí)候可以直接寫回對(duì)應(yīng)的硬盤文件里,即Page-out
。而Anonymous Pages
在內(nèi)存不足時(shí)就只能寫到硬盤上的交換區(qū)Swap
里來釋放內(nèi)存,稱之為Swap-out
。
Anonymous Pages
與用戶進(jìn)程共存,進(jìn)程退出則Anonymous pages
釋放,而Page Cache
即使在進(jìn)程退出后還可以緩存。
下面是磁盤緩存相關(guān)的統(tǒng)計(jì)項(xiàng):
-
Buffers — 塊設(shè)備所占用的緩存頁,比如磁盤文件系統(tǒng)的
meta
信息如SuperBlock
等,直接讀寫塊設(shè)備產(chǎn)生的緩存也統(tǒng)計(jì)在這里(例如dd
命令) -
Cached — 從磁盤讀取的文件內(nèi)容緩存(即
Page cache
) -
SwapCached —
Swap
中包含的確定要被換出,但是尚未寫入物理交換區(qū)的匿名內(nèi)存頁 -
SwapTotal — 可用的磁盤
Swap
總大小 -
SwapFree — 磁盤
Swap
的free
大小 -
Dirty — 修改了等待寫回磁盤的內(nèi)存大小
-
Writeback — 正在寫回磁盤的內(nèi)存大小
以下幾項(xiàng)和內(nèi)核的頁面回收算法(Page Frame Reclaiming)
相關(guān),Page Cache
和所有用戶進(jìn)程的內(nèi)存(除內(nèi)核棧和HugePages
外)都在相關(guān)的LRU Lists
上。內(nèi)核在 2.6 以前就引入了增強(qiáng)的LRU算法
來解決樸素的LRU算法
完全不考慮使用頻率的問題。具體的Active 鏈表
和Inactive 鏈表
的使用詳情請(qǐng)參閱其他資料。
-
Active — 最近使用的內(nèi)存,回收的優(yōu)先級(jí)低
-
Inactive — 最近較少使用的內(nèi)存,回收的優(yōu)先級(jí)高
-
Active (anon) —
Active 鏈表
中的匿名頁(Anonymous Pages
)部分 -
Inactive (anon) —
Inactive 鏈表
中的匿名頁(Anonymous Pages
)部分 -
Active (file) —
Active 鏈表
中的File-backed Pages
部分 -
Inactive (file) —
Inactive 鏈表
中的File-backed Pages
部分 -
Unevictable — 禁止換出的頁,對(duì)應(yīng)
Unevictable 鏈表
,其中包括VM_LOCKED
的內(nèi)存頁、SHM_LOCK
的共享內(nèi)存頁(也統(tǒng)計(jì)在Mlocked
中)、和Ramfs
等 -
Mlocked —
mlock
系統(tǒng)調(diào)用鎖定的內(nèi)存大小
共享內(nèi)存在 Linux 中細(xì)分的話可以分為以下幾種:
-
SystemV Shared Memory —
shmget
-
POSIX Shared Memory —
shm_open
-
Shared Anonymous Memory —
mmap(MAP_ANONYMOUS | MAP_SHARED)
共享內(nèi)存在內(nèi)核中都是 基于tmpf機(jī)制實(shí)現(xiàn) 的。因?yàn)榛谖募到y(tǒng)所以就不能算是匿名頁,不能計(jì)入AnonPages
的統(tǒng)計(jì)項(xiàng),而只能計(jì)入Cached
和MApped
統(tǒng)計(jì)項(xiàng)。但是,tmpfs
背后并沒有真實(shí)的磁盤文件存在,如果想要被臨時(shí)釋放出來,只能通過Swap
的方式,所以內(nèi)存頁被鏈接到了Inactive(anon)
和Active(anon)
里。
也就是說,共享內(nèi)存的頁面屬于File-backed Pages
,但是被放在Inactive(anon)
和Active(anon)
鏈表里,統(tǒng)計(jì)也不算在AnonPages
里,而是算在Cached
和Mapped
里。特別地,如果這些頁被mlock
的話,就放在Unevictable
鏈里并計(jì)算在內(nèi)。所以從數(shù)值上看,Inactive(anon)
項(xiàng) +Active(anon)
項(xiàng) 不等于AnonPages
項(xiàng),因?yàn)榍罢甙ü蚕韮?nèi)存的部分。Active(file)
項(xiàng) +Inactive(file)
項(xiàng) 也不等于Mapped
項(xiàng),因?yàn)榍罢咧邪?code>Unmapped的內(nèi)存,后者還包含共享內(nèi)存的部分(這部分在Inactive(anon)
和Active(anon)
里)。
這里有一個(gè)情況要注意,與文件關(guān)聯(lián)的頁也有可能是匿名頁(MAP_PRIVATE
映射的頁面被修改時(shí)會(huì)產(chǎn)生一個(gè)匿名頁拷貝),會(huì)被算到AnonPages
里。
與此相關(guān)的相關(guān)的統(tǒng)計(jì)項(xiàng)有:
-
AnonPages — 匿名頁(
Anonymous pages
)的大小,同時(shí)也包含Transparent HugePages (THP)
對(duì)應(yīng)的 AnonHugePages -
Mapped — 設(shè)備和文件等映射的大小,
Mapped
統(tǒng)計(jì)了Cached
中所有的Mapped
頁面,是Cached
的子集(滿足Cached
-Mapped
=Unmapped
)。共享內(nèi)存、可執(zhí)行程序的文件、動(dòng)態(tài)庫、mmap
的文件等都統(tǒng)計(jì)在這里 -
Shmem — 共享內(nèi)存的大小,包括
Shared Memory
、tmpfs
和devtmpfs
注意 Linux 的內(nèi)存是真正使用時(shí)才分配的,所以注意這里的大小都是已分配的大小,而不是程序里申請(qǐng)的大小。
下面都是內(nèi)核使用的內(nèi)存相關(guān)的統(tǒng)計(jì)項(xiàng):
-
Slab — 內(nèi)核
Slab
結(jié)構(gòu)使用的大小(就是那個(gè)Slab分配器占用的) -
SReclaimable — 內(nèi)核
Slab
里面可回收的部分(調(diào)用kmem_getpages()
時(shí)帶有 SLAB_RECLAIM_ACCOUNT 標(biāo)的) -
SUnreclaim —
Slab
里面無法回收的大小,等于Slab
項(xiàng) -SReclaimable
項(xiàng) -
KernelStack — 分配給內(nèi)核棧的大小(每個(gè)用戶線程都會(huì)分配一個(gè)
Kernel Stack
,系統(tǒng)調(diào)用syscall
、trap
、exception
后進(jìn)入內(nèi)核態(tài)執(zhí)行代碼時(shí)候使用) -
PageTables — 頁表的大小(就是經(jīng)常掛在嘴上的那個(gè)頁表)
-
NFS_Unstable — 發(fā)送到服務(wù)端但尚未提交的 NFS 頁的大小
-
Bounce — 塊設(shè)備 “bounce buffers” 部分的大小(有些老設(shè)備只能訪問低端內(nèi)存,比如 16M 以下,這部分分配的 buffer 統(tǒng)計(jì)在這里)
-
WritebackTmp — FUSE 用于寫回磁盤的緩沖區(qū)的大小
-
VmallocTotal — vmalloc 區(qū)域大小
-
VmallocUsed — vmalloc 區(qū)域使用大小
-
VmallocChunk — vmalloc 區(qū)域最大的 free 連續(xù)區(qū)塊大小
-
HardwareCorrupted — 系統(tǒng)檢測(cè)到內(nèi)存的硬件故障的內(nèi)存大小(問題頁會(huì)被記錄不再使用)
之前說過,HugePages 是獨(dú)立統(tǒng)計(jì)的,如果進(jìn)程使用了 HugePages,是不會(huì)計(jì)入自身的RSS/PSS
的。注意下面的AnonHugePages
指的是透明大頁(THP,Transparent HugePages),THP
是統(tǒng)計(jì)在進(jìn)程的RSS/PSS
里的,要注意區(qū)別。下面是相關(guān)的統(tǒng)計(jì)項(xiàng):
-
AnonHugePages — 透明大頁 THP 使用的大小
-
HugePages_Total — 內(nèi)存大頁的總量,對(duì)應(yīng)
/proc/sys/vm/nr_hugepages
,可以動(dòng)態(tài)改 -
HugePages_Free — 內(nèi)存大頁中 free 的大小
-
HugePages_Rsvd — 內(nèi)存大頁中能分配出來的大小
-
HugePages_Surp — 內(nèi)存大頁中超過
/proc/sys/vm/nr_hugepages
的大小, 最大值由/proc/sys/vm/nr_overcommit_hugepages
限制 -
Hugepagesize — 內(nèi)存大頁的頁大小
進(jìn)程級(jí)別的統(tǒng)計(jì)
先介紹幾個(gè)通用概念:
-
VSS -
Virtual Set Size
,虛擬內(nèi)存大小,包含共享庫占用的全部?jī)?nèi)存,以及分配但未使用內(nèi)存 -
RSS -
Resident Set Size
,實(shí)際使用物理內(nèi)存,包含了共享庫占用的全部?jī)?nèi)存 -
PSS -
Proportional Set Size
,實(shí)際使用的物理內(nèi)存,共享庫占用的內(nèi)存按照進(jìn)程數(shù)等比例劃分 -
USS -
Unique Set Size
,進(jìn)程獨(dú)自占用的物理內(nèi)存,不包含共享庫占用的內(nèi)存
/proc/{pid}/smaps 文件
在/proc/{pid}/smaps
文件對(duì)應(yīng)每個(gè)進(jìn)程的詳細(xì)內(nèi)存分段統(tǒng)計(jì)。截取一部分:
下面分別解釋下含義:
-
Size:映射的大小(
mapping size
) -
Rss:實(shí)際駐留在
RAM
的內(nèi)存大小(包括共享庫的大小,不包括已經(jīng)交換出去的頁面) -
Pss:Rss 的基礎(chǔ)上,把共享庫的大小均攤給所有被映射的進(jìn)程后的大小
-
Shared_Clean:共享的
Clean
內(nèi)存的大小 -
Shared_Dirty:共享的
Dirty
內(nèi)存的大小 -
Private_Clean:私有的
Clean
內(nèi)存的大小 -
Private_Dirty:私有的
Dirty
內(nèi)存的大小 -
Referenced:當(dāng)前被標(biāo)記為引用的頁的大小
-
Anonymous:匿名內(nèi)存的大小
-
AnonHugePages:透明大頁內(nèi)存的大小
-
Swap:
Swap
的大小 -
KernelPageSize:內(nèi)核頁大小
-
MMUPageSize:
MMU
頁大小 -
Locked:被
mlock
的內(nèi)存大小 -
VmFlags:頁的標(biāo)志位,有點(diǎn)多這里不列舉,詳見參考資料 [4]
可以看到Rss
這個(gè)指標(biāo)實(shí)際上是包含了共享庫的大小的,不同的進(jìn)程會(huì)共享這個(gè)映射的,如果想通過累加這個(gè)值來計(jì)算所有進(jìn)程用到的內(nèi)存的話就不準(zhǔn)確了,而Pss
把共享庫的大小均攤給了所有用到映射了這個(gè)庫的進(jìn)程,所以累加起來就不會(huì)重復(fù)計(jì)算共享庫大小了。
P.S. 最新的內(nèi)核文檔提到了要加
smaps_rollup
這個(gè)統(tǒng)計(jì),支持Pss_Anon
、Pss_File
和Pss_Shmem
三個(gè)分類統(tǒng)計(jì),這個(gè)在進(jìn)程級(jí)別看,用到內(nèi)存就很清晰了。
我們可以累加一下這個(gè)值看看某進(jìn)程用到的內(nèi)存總和:
注意單位是KB,所以這里進(jìn)程用到的內(nèi)存是 1.17 GB 左右。
這是個(gè)使用共享內(nèi)存作為存儲(chǔ)的服務(wù),所以這是符合預(yù)期的。如果想要看排除共享內(nèi)存的部分,那要看Anonymous
部分的總和:
所以實(shí)際匿名內(nèi)存使用是 63 MB 左右。
top 命令
top
命令中關(guān)于內(nèi)存使用的統(tǒng)計(jì):
內(nèi)存相關(guān)的統(tǒng)計(jì)有VIRT
、RES
、SHR
、SWAP
、CODE
、DATA
、USED
-
VIRT —
Virtual Memory Size
,虛擬內(nèi)存大小,包括所有代碼、數(shù)據(jù)和共享庫,以及已交換的頁面和已映射但未使用的內(nèi)存 -
RES —
Resident Memory Size
,駐留內(nèi)存大小,共享的內(nèi)存比如動(dòng)態(tài)庫也會(huì)計(jì)算在內(nèi) -
SHR —
Shared Memory Size
,共享的內(nèi)存大小,并非所有共享的內(nèi)存都是常駐的 -
SWAP — Swapped Size,非駐留內(nèi)存大小
-
CODE — Code Size,程序可執(zhí)行代碼的大小
-
DATA — Data + Stack Size,可執(zhí)行代碼以外的物理內(nèi)存量,也稱為數(shù)據(jù)駐留集大小
-
USED — Memory in Use,RES + SWAP 的大小
其他的內(nèi)存查看命令
常用的還有這些:vmstat
、sar
、slabtop
、kmstat
、ps
、prstat
、pmap
等等。懶得寫了,有問題看man
文檔得了。
參考文獻(xiàn)
[1] Understanding the Linux Kernel, Daniel Plerre Bovet / Marco Cesati, 2005-11
[2] Professional Linux Kernel Architecture, Wolfgang Mauerer, 2008-10-13
[3] Systems Performance: Enterprise and the Cloud, Brendan Gregg, 2013-10-26
[4] https://raw.githubusercontent.com/torvalds/linux/master/Documentation/filesystems/proc.txt
[5] https://en.wikipedia.org/wiki/Resident_set_size
[6] https://en.wikipedia.org/wiki/Proportional_set_size
[7] https://en.wikipedia.org/wiki/Unique_set_size