notes
- 32位每窖,3G用戶空間帮掉,1G核心空間。
- 64位窒典,內(nèi)核和用戶均為128T蟆炊,剩下的未定義。用戶低位瀑志,核心高位涩搓。
- 內(nèi)存分布污秆。
只讀段,包括代碼和常量等昧甘。
數(shù)據(jù)段良拼,包括全局變量等。
堆疾层,包括動(dòng)態(tài)分配的內(nèi)存将饺,從低地址開始向上增長(zhǎng)。
文件映射段痛黎,包括動(dòng)態(tài)庫予弧、共享內(nèi)存等,從高地址開始向下增長(zhǎng)湖饱。
棧掖蛤,包括局部變量和函數(shù)調(diào)用的上下文等。棧的大小是固定的井厌,一般是 8 MB蚓庭。 - 內(nèi)存分配涉及兩種系統(tǒng)調(diào)用:
對(duì)小塊內(nèi)存(小于 128K),C 標(biāo)準(zhǔn)庫使用 brk() 來分配仅仆,也就是通過移動(dòng)堆頂?shù)奈恢脕矸峙鋬?nèi)存器赞。這些內(nèi)存釋放后并不會(huì)立刻歸還系統(tǒng),而是被緩存起來墓拜,這樣就可以重復(fù)使用港柜。
而大塊內(nèi)存(大于 128K),則直接使用內(nèi)存映射 mmap() 來分配咳榜,也就是在文件映射段找一塊空閑內(nèi)存分配出去夏醉。 - 回收不常訪問的內(nèi)存時(shí),會(huì)用到交換分區(qū)(以下簡(jiǎn)稱 Swap)涌韩。Swap 其實(shí)就是把一塊磁盤空間當(dāng)成內(nèi)存來用畔柔。它可以把進(jìn)程暫時(shí)不用的數(shù)據(jù)存儲(chǔ)到磁盤中(這個(gè)過程稱為換出),當(dāng)進(jìn)程訪問這些內(nèi)存時(shí)臣樱,再從磁盤讀取這些數(shù)據(jù)到內(nèi)存中(這個(gè)過程稱為換入)
- oom,管理員可以通過 /proc 文件系統(tǒng)靶擦,手動(dòng)設(shè)置進(jìn)程的 oom_adj ,從而調(diào)整進(jìn)程的 oom_score雇毫。oom_adj 的范圍是 [-17, 15]奢啥,數(shù)值越大,表示進(jìn)程越容易被 OOM 殺死嘴拢;數(shù)值越小,表示進(jìn)程越不容易被 OOM 殺死寂纪,其中 -17 表示禁止 OOM席吴。
【思考】有的時(shí)候發(fā)生oom赌结,是不是也要思考一下是不是可以調(diào)整oom_adj,因?yàn)閮?yōu)化進(jìn)程的效率比較低孝冒。 - 緩存分buffer和cache
- Buffers 是內(nèi)核緩沖區(qū)用到的內(nèi)存柬姚,對(duì)應(yīng)的是 /proc/meminfo 中的 Buffers 值。
Buffers 是對(duì)原始磁盤塊的臨時(shí)存儲(chǔ)庄涡,也就是用來緩存磁盤的數(shù)據(jù)量承,通常不會(huì)特別大(20MB 左右)。這樣穴店,內(nèi)核就可以把分散的寫集中起來撕捍,統(tǒng)一優(yōu)化磁盤的寫入,比如可以把多次小的寫合并成單次大的寫等等泣洞。 - Cache 是內(nèi)核頁緩存和 Slab 用到的內(nèi)存忧风,對(duì)應(yīng)的是 /proc/meminfo 中的 Cached 與 SReclaimable 之和。
Cached 是從磁盤讀取文件的頁緩存球凰,也就是用來緩存從文件讀取的數(shù)據(jù)狮腿。這樣,下次訪問這些文件數(shù)據(jù)時(shí)呕诉,就可以直接從內(nèi)存中快速獲取缘厢,而不需要再次訪問緩慢的磁盤。
SReclaimable 是 Slab 的一部分甩挫。Slab 包括兩部分贴硫,其中的可回收部分,用 SReclaimable 記錄捶闸;而不可回收部分夜畴,用 SUnreclaim 記錄。
其實(shí):Buffer 是對(duì)磁盤數(shù)據(jù)的緩存删壮,而 Cache 是文件數(shù)據(jù)的緩存贪绘,它們既會(huì)用在讀請(qǐng)求中,也會(huì)用在寫請(qǐng)求中央碟。
- Linux 提供了一個(gè) /proc/sys/vm/swappiness 選項(xiàng)税灌,用來調(diào)整使用 Swap 的積極程度。swappiness 的范圍是 0-100亿虽,數(shù)值越大菱涤,越積極使用 Swap,也就是更傾向于回收匿名頁洛勉;數(shù)值越小粘秆,越消極使用 Swap,也就是更傾向于回收文件頁收毫。
可以設(shè)置 /proc/sys/vm/min_free_kbytes攻走,來調(diào)整系統(tǒng)定期回收內(nèi)存的閾值(也就是頁低閾值)殷勘,還可以設(shè)置 /proc/sys/vm/swappiness,來調(diào)整文件頁和匿名頁的回收傾向昔搂。
tools
free
# 注意不同版本的free輸出可能會(huì)有所不同
$ free
total used free shared buff/cache available
Mem: 8169348 263524 6875352 668 1030472 7611064
Swap: 0 0 0
第一列玲销,total 是總內(nèi)存大小摘符;
第二列贤斜,used 是已使用內(nèi)存的大小,包含了共享內(nèi)存逛裤;
第三列瘩绒,free 是未使用內(nèi)存的大小别凹;
第四列草讶,shared 是共享內(nèi)存的大小炉菲;
第五列堕战,buff/cache 是緩存和緩沖區(qū)的大小拍霜;
最后一列嘱丢,available 是新進(jìn)程可用內(nèi)存的大小。
top
# 按下M切換到內(nèi)存排序
$ top
...
KiB Mem : 8169348 total, 6871440 free, 267096 used, 1030812 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 7607492 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
430 root 19 -1 122360 35588 23748 S 0.0 0.4 0:32.17 systemd-journal
1075 root 20 0 771860 22744 11368 S 0.0 0.3 0:38.89 snapd
1048 root 20 0 170904 17292 9488 S 0.0 0.2 0:00.24 networkd-dispat
1 root 20 0 78020 9156 6644 S 0.0 0.1 0:22.92 systemd
12376 azure 20 0 76632 7456 6420 S 0.0 0.1 0:00.01 systemd
12374 root 20 0 107984 7312 6304 S 0.0 0.1 0:00.00 sshd
...
VIRT 是進(jìn)程虛擬內(nèi)存的大小祠饺,只要是進(jìn)程申請(qǐng)過的內(nèi)存越驻,即便還沒有真正分配物理內(nèi)存,也會(huì)計(jì)算在內(nèi)道偷。
RES 是常駐內(nèi)存的大小缀旁,也就是進(jìn)程實(shí)際使用的物理內(nèi)存大小,但不包括 Swap 和共享內(nèi)存勺鸦。
SHR 是共享內(nèi)存的大小并巍,比如與其他進(jìn)程共同使用的共享內(nèi)存、加載的動(dòng)態(tài)鏈接庫以及程序的代碼段等换途。
%MEM 是進(jìn)程使用物理內(nèi)存占系統(tǒng)總內(nèi)存的百分比懊渡。
tips:
第一,虛擬內(nèi)存通常并不會(huì)全部分配物理內(nèi)存军拟。從上面的輸出剃执,你可以發(fā)現(xiàn)每個(gè)進(jìn)程的虛擬內(nèi)存都比常駐內(nèi)存大得多。
第二懈息,共享內(nèi)存 SHR 并不一定是共享的肾档,比方說,程序的代碼段辫继、非共享的動(dòng)態(tài)鏈接庫阁最,也都算在 SHR 里戒祠。當(dāng)然,SHR 也包括了進(jìn)程間真正共享的內(nèi)存速种。所以在計(jì)算多個(gè)進(jìn)程的內(nèi)存使用時(shí),不要把所有進(jìn)程的 SHR 直接相加得出結(jié)果低千。
cachestat 提供了整個(gè)系統(tǒng)緩存的讀寫命中情況配阵。
$ cachestat 1 3
TOTAL MISSES HITS DIRTIES BUFFERS_MB CACHED_MB
2 0 2 1 17 279
2 0 2 1 17 279
2 0 2 1 17 279
TOTAL ,表示總的 I/O 次數(shù)示血;
MISSES 棋傍,表示緩存未命中的次數(shù);
HITS 难审,表示緩存命中的次數(shù)瘫拣;
DIRTIES, 表示新增到緩存中的臟頁數(shù)告喊;
BUFFERS_MB 表示 Buffers 的大小麸拄,以 MB 為單位;
CACHED_MB 表示 Cache 的大小黔姜,以 MB 為單位拢切。
cachetop 提供了每個(gè)進(jìn)程的緩存命中情況。
$ cachetop
11:58:50 Buffers MB: 258 / Cached MB: 347 / Sort: HITS / Order: ascending
PID UID CMD HITS MISSES DIRTIES READ_HIT% WRITE_HIT%
13029 root python 1 0 0 100.0% 0.0%
它的輸出跟 top 類似秆吵,默認(rèn)按照緩存的命中次數(shù)(HITS)排序淮椰,展示了每個(gè)進(jìn)程的緩存命中情況。具體到每一個(gè)指標(biāo)纳寂,這里的 HITS主穗、MISSES 和 DIRTIES ,跟 cachestat 里的含義一樣毙芜,分別代表間隔時(shí)間內(nèi)的緩存命中次數(shù)忽媒、未命中次數(shù)以及新增到緩存中的臟頁數(shù)。
而 READ_HIT 和 WRITE_HIT 爷肝,分別表示讀和寫的緩存命中率猾浦。
pcstat (指定文件在內(nèi)存中的緩存大小)
$ pcstat /bin/ls
+---------+----------------+------------+-----------+---------+
| Name | Size (bytes) | Pages | Cached | Percent |
|---------+----------------+------------+-----------+---------|
| /bin/ls | 133792 | 33 | 0 | 000.000 |
+---------+----------------+------------+-----------+---------+
memleak
# -a 表示顯示每個(gè)內(nèi)存分配請(qǐng)求的大小以及地址
# -p 指定案例應(yīng)用的PID號(hào)
$ /usr/share/bcc/tools/memleak -a -p $(pidof app)
WARNING: Couldn't find .text section in /app
WARNING: BCC can't handle sym look ups for /app
addr = 7f8f704732b0 size = 8192
addr = 7f8f704772d0 size = 8192
addr = 7f8f704712a0 size = 8192
addr = 7f8f704752c0 size = 8192
32768 bytes in 4 allocations from stack
[unknown] [app]
[unknown] [app]
start_thread+0xdb [libpthread-2.27.so]
這里有一個(gè)問題,Couldn’t find .text section in /app灯抛,所以調(diào)用棧不能正常輸出金赦,最后的調(diào)用棧部分只能看到 [unknown] 的標(biāo)志。為什么會(huì)有這個(gè)錯(cuò)誤呢对嚼?實(shí)際上夹抗,這是由于案例應(yīng)用運(yùn)行在容器中導(dǎo)致的。
$ docker cp app:/app /app
$ /usr/share/bcc/tools/memleak -p $(pidof app) -a
Attaching to pid 12512, Ctrl+C to quit.
[03:00:41] Top 10 stacks with outstanding allocations:
addr = 7f8f70863220 size = 8192
addr = 7f8f70861210 size = 8192
addr = 7f8f7085b1e0 size = 8192
addr = 7f8f7085f200 size = 8192
addr = 7f8f7085d1f0 size = 8192
40960 bytes in 5 allocations from stack
fibonacci+0x1f [app]
child+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]
utility
- 使用top和ps查詢系統(tǒng)中大量占用內(nèi)存的進(jìn)程纵竖,使用cat /proc/[pid]/status和pmap -x pid查看某個(gè)進(jìn)程使用內(nèi)存的情況和動(dòng)態(tài)變化
- 發(fā)生oom,查看哪個(gè)進(jìn)程被kill
dmesg |grep -E ‘kill|oom|out of memory’ - 查看各個(gè)進(jìn)程的實(shí)際物理內(nèi)存使用
從 /proc/< pid >/smaps 入手
動(dòng)手查 proc 文件系統(tǒng)的文檔 - dd命令也支持直接IO的 有選項(xiàng)oflag和iflag 所以dd也可以用來繞過cache buff做測(cè)試
- 最好禁止 Swap漠烧。如果必須開啟 Swap杏愤,降低 swappiness 的值,減少內(nèi)存回收時(shí) Swap 的使用傾向已脓。
- 減少內(nèi)存的動(dòng)態(tài)分配珊楼。比如,可以使用內(nèi)存池度液、大頁(HugePage)等厕宗。
- 盡量使用緩存和緩沖區(qū)來訪問數(shù)據(jù)。比如堕担,可以使用堆棧明確聲明內(nèi)存空間已慢,來存儲(chǔ)需要緩存的數(shù)據(jù);或者用 Redis 這類的外部緩存組件霹购,優(yōu)化數(shù)據(jù)的訪問佑惠。
- 使用 cgroups 等方式限制進(jìn)程的內(nèi)存使用情況。這樣齐疙,可以確保系統(tǒng)內(nèi)存不會(huì)被異常進(jìn)程耗盡膜楷。
- 通過 /proc/pid/oom_adj ,調(diào)整核心應(yīng)用的 oom_score剂碴。這樣把将,可以保證即使內(nèi)存緊張,核心應(yīng)用也不會(huì)被 OOM 殺死忆矛。
KKK快速定位性能問題
具體的分析思路主要有這幾步察蹲。
先用 free 和 top,查看系統(tǒng)整體的內(nèi)存使用情況催训。
再用 vmstat 和 pidstat洽议,查看一段時(shí)間的趨勢(shì),從而判斷出內(nèi)存問題的類型漫拭。
最后進(jìn)行詳細(xì)分析亚兄,比如內(nèi)存分配分析、緩存 / 緩沖區(qū)分析采驻、具體進(jìn)程的內(nèi)存使用分析等审胚。
第一個(gè)例子,當(dāng)你通過 free礼旅,發(fā)現(xiàn)大部分內(nèi)存都被緩存占用后膳叨,可以使用 vmstat 或者 sar 觀察一下緩存的變化趨勢(shì),確認(rèn)緩存的使用是否還在繼續(xù)增大痘系。
如果繼續(xù)增大菲嘴,則說明導(dǎo)致緩存升高的進(jìn)程還在運(yùn)行,那你就能用緩存 / 緩沖區(qū)分析工具(比如 cachetop、slabtop 等)龄坪,分析這些緩存到底被哪里占用昭雌。
第二個(gè)例子,當(dāng)你 free 一下健田,發(fā)現(xiàn)系統(tǒng)可用內(nèi)存不足時(shí)烛卧,首先要確認(rèn)內(nèi)存是否被緩存 / 緩沖區(qū)占用。排除緩存 / 緩沖區(qū)后妓局,你可以繼續(xù)用 pidstat 或者 top唱星,定位占用內(nèi)存最多的進(jìn)程。
找出進(jìn)程后跟磨,再通過進(jìn)程內(nèi)存空間工具(比如 pmap),分析進(jìn)程地址空間中內(nèi)存的使用情況就可以了攒盈。
第三個(gè)例子抵拘,當(dāng)你通過 vmstat 或者 sar 發(fā)現(xiàn)內(nèi)存在不斷增長(zhǎng)后,可以分析中是否存在內(nèi)存泄漏的問題型豁。
比如你可以使用內(nèi)存分配分析工具 memleak 僵蛛,檢查是否存在內(nèi)存泄漏。如果存在內(nèi)存泄漏問題迎变,memleak 會(huì)為你輸出內(nèi)存泄漏的進(jìn)程以及調(diào)用堆棧充尉。