:進(jìn)程內(nèi)存占用查看與系統(tǒng)排查技巧)
排查Linux服務(wù)器問題的時候我干的第一件事永遠(yuǎn)是敲下top。這三個字母看起來簡單但它能告訴你的東西幾乎等于半臺服務(wù)器的體檢報告——哪個進(jìn)程在搶CPU誰把系統(tǒng)內(nèi)存吃掉了磁盤IO忙不忙load average是不是已經(jīng)爆了一眼就能掃出來。今天這篇文章就圍繞“top命令的使用”和“查看某個進(jìn)程占用的系統(tǒng)內(nèi)存大小”這兩條主線展開把top從啟動到交互、從單次查看到腳本化監(jiān)控的全套玩法拆開來聊。無論你是剛接觸Linux的開發(fā)者還是天天被線上告警折磨的運維這篇都值得存一份。文章不會只停留在“按個M看內(nèi)存排序”這種說明書層面我會把進(jìn)程和系統(tǒng)內(nèi)存之間的對應(yīng)關(guān)系、top輸出里每一列的真實含義、常見誤區(qū)和實際排查經(jīng)驗都串起來講。畢竟工具本身很簡單真正難的是你知道自己在看什么。1. top命令使用前的核心認(rèn)知很多人一上來就敲top然后被滿屏數(shù)字嚇到。其實top的設(shè)計初衷很樸素它就是一個能持續(xù)刷新、可以交互操作的進(jìn)程實時監(jiān)視器。想用好它你得先搞清楚兩件事——top到底給你看什么以及它顯示的“內(nèi)存”到底指什么。1.1 top是什么一個能持續(xù)刷新的進(jìn)程實時監(jiān)視器Linux下監(jiān)控系統(tǒng)資源有很多命令ps、free、vmstat、htop各有各的適用場景。top最特殊的地方在于它默認(rèn)是“活的”啟動之后每隔幾秒自動刷新一次而且能在界面里直接排序、過濾、發(fā)信號給進(jìn)程。你想象一下ps是拿手機給體檢報告拍張照片top是直接連上心電圖機實時看波形這就是本質(zhì)區(qū)別。所以top特別適合兩個場景第一系統(tǒng)還在持續(xù)運行中你想動態(tài)觀察資源變化第二你已經(jīng)發(fā)現(xiàn)某個進(jìn)程或某項資源不對勁想在交互界面里快速展開定位。比如服務(wù)器突然CPU跑滿你用ps aux看到的只是一瞬間的狀態(tài)CPU占用率可能下一秒就變成別的進(jìn)程了。而top會一直刷新誰持續(xù)把CPU吃滿在界面里一目了然。top還有一個容易被忽略的身份批處理模式下它其實是一個非常好用的“監(jiān)控數(shù)據(jù)輸出工具”。后面我會詳細(xì)講top -b -n這種用法很多自己寫的巡檢腳本、告警腳本里最核心的數(shù)據(jù)源就是從top批處理輸出里解析出來的。1.2 先把內(nèi)存指標(biāo)讀明白VIRT、RES、SHR、%MEM看進(jìn)程的內(nèi)存占用top列表里有幾列名字特別容易混淆VIRT、RES、SHR、%MEM。這四個概念不搞明白你就會被“某個進(jìn)程占了多少內(nèi)存”這個問題反復(fù)誤導(dǎo)。我用最直白的方式解釋一遍。VIRTVirtual Memory Size是進(jìn)程申請的虛擬內(nèi)存總量包括進(jìn)程自己申請的堆、棧以及它映射的共享庫、內(nèi)存映射文件等。虛擬內(nèi)存可以很大但它不代表真正占用了物理內(nèi)存。你可以把它理解成一個人“理論上需要準(zhǔn)備的辦公空間”——哪怕只是紙上規(guī)劃了一大片區(qū)域?qū)嶋H的桌子還沒鋪開。RESResident Memory Size才是進(jìn)程當(dāng)前實際駐留在物理內(nèi)存中的部分也就是真正占用了物理內(nèi)存條空間的大小。這才是“這個進(jìn)程到底吃了多少內(nèi)存”最值得看的指標(biāo)。top內(nèi)存排序和%MEM計算本質(zhì)上都是基于這個數(shù)值。SHRShared Memory Size是進(jìn)程使用的共享內(nèi)存量包括共享庫、共享內(nèi)存段等。多個進(jìn)程可以同時引用同一份共享庫代碼這份庫在物理內(nèi)存里只存一份但每個進(jìn)程的RES里都會計入自己引用的一部分。所以當(dāng)你用top按RES把所有進(jìn)程加起來再對比free顯示的總used內(nèi)存會發(fā)現(xiàn)“加起來對不上”原因之一就是SHR被重復(fù)計算了。這點后面的踩坑部分會再展開。%MEM就簡單了進(jìn)程的RES除以物理內(nèi)存總量再乘以100表示這個進(jìn)程占用了系統(tǒng)物理內(nèi)存的百分比。它和RES是強綁定的關(guān)系RES漲它就漲RES跌它就跌。2. 快速上手top的基本操作與交互快捷鍵工具不吃透快捷鍵使用效率會低一半。top啟動很簡單終端敲top回車就行但啟動之后的界面信息怎么讀、怎么讓它按你的思路展示數(shù)據(jù)這里面有不少門道。2.1 首次運行top界面信息怎么讀運行top后屏幕上半部分是匯總區(qū)下半部分是進(jìn)程列表。匯總區(qū)前幾行信息量很大。第一行依次是當(dāng)前時間、系統(tǒng)已運行時間、登錄用戶數(shù)、系統(tǒng)負(fù)載load average的1分鐘/5分鐘/15分鐘平均值。負(fù)載值不是百分比它是一個“等待調(diào)度的進(jìn)程數(shù)正在運行的進(jìn)程數(shù)”的加權(quán)值一般經(jīng)驗是負(fù)載值乘以100以后和CPU核心數(shù)對比明顯大于100%才叫過載。第二行是任務(wù)匯總total進(jìn)程總數(shù)、running運行中、sleeping睡眠、stopped停止、zombie僵尸。重點看zombie如果長期存在僵尸進(jìn)程通常是父進(jìn)程沒有正確回收子進(jìn)程資源。第三行是CPU狀態(tài)匯總us用戶態(tài)占用、sy內(nèi)核態(tài)占用、ni優(yōu)先級調(diào)整過的進(jìn)程占用、id空閑、wa等待IO、hi硬件中斷、si軟件中斷、st被虛擬機偷走的時間。線上排查CPU問題先看us高還是sy高us高是業(yè)務(wù)代碼在燒CPUsy高要懷疑系統(tǒng)調(diào)用頻繁或內(nèi)核線程異常wa高則說明磁盤或網(wǎng)絡(luò)IO才是瓶頸。第四行和第五行分別是物理內(nèi)存和交換分區(qū)信息total總量、free完全空閑、used已使用、buff/cache用作內(nèi)核緩沖區(qū)/頁緩存的部分。很多新手會把used直接當(dāng)成“系統(tǒng)內(nèi)存真的不夠了”其實內(nèi)核的cache是可以按需回收的真實可用內(nèi)存得看free命令里available那列或者用used減去buff/cache部分再判斷。這點后面我還會強調(diào)。2.2 交互模式下必須掌握的快捷鍵進(jìn)程列表默認(rèn)是按CPU占用率從高到低排序但實際大家更喜歡看一眼內(nèi)存占用情況。在top交互界面里按鍵瞬間生效而且不用按回車按M進(jìn)程列表按內(nèi)存占用RES從高到低排序想找“誰在吃內(nèi)存”就按這個。按P按CPU占用率從高到低排序這是默認(rèn)排序方式。按N按PID數(shù)字大小排序。按T按累計運行時間排序適合看誰長期霸占CPU。按c顯示完整命令行很多進(jìn)程默認(rèn)只顯示名字按c之后能看到它啟動時的完整參數(shù)定位問題特別實用。按1展開或折疊多核CPU的每個核狀態(tài)很多top版本默認(rèn)把所有核平均值合成一行。按u輸入用戶名只顯示這個用戶的進(jìn)程。按o輸入過濾表達(dá)式比按用戶名更靈活。比如輸入COMMANDjava就只看命令行里帶java的進(jìn)程。按k輸入PID后可以給進(jìn)程發(fā)送信號默認(rèn)是15SIGTERM輸入9就是強殺。按r修改進(jìn)程優(yōu)先級renice數(shù)值越小優(yōu)先級越高。按W保存當(dāng)前配置下次啟動top還是這個布局。按q退出。這里我想單獨說下o過濾表達(dá)式它的能力比很多老運維平時用到的都強。格式是字段值支持正則表達(dá)式比如o之后輸入%MEM5.0或者RES1048576界面里就只顯示物理內(nèi)存占用超過1GB的進(jìn)程。這個在做大內(nèi)存問題排查時非常好用不信你試試屏幕上瞬間干凈。2.3 批處理模式讓top變成可腳本化的數(shù)據(jù)源交互模式適合人來操作但如果你想把top的輸出拿去存日志、做告警或者寫進(jìn)定時任務(wù)里就需要批處理模式。命令是top -b -n 2 -d 3-b表示批處理模式不會進(jìn)入交互界面直接往標(biāo)準(zhǔn)輸出打印結(jié)果-n 2表示輸出2幀-d 3表示每幀間隔3秒。批處理模式下的輸出和交互模式基本一致只是不會刷新光標(biāo)而是逐幀打印非常適合重定向到文件里。這里有一個很多人踩過的坑top -b -n 1抓出來的第一幀CPU占用率往往是不準(zhǔn)的。因為top剛啟動時還沒有足夠的時間窗口去統(tǒng)計CPU使用率第一幀顯示的是從開機到當(dāng)前的累計平均狀態(tài)而不是最近一個刷新周期的狀態(tài)。所以我自己的習(xí)慣是至少抓兩幀取第二幀。再配合| head或者awk就能從輸出中提取你需要的那部分。比如我想知道當(dāng)前有沒有進(jìn)程CPU占用超過80%可以這樣top -b -n 2 -d 3 | awk NR7 $9 80.0 {print}NR7是跳過頂部匯總信息層直接從進(jìn)程列表開始處理。實際使用時可能需要根據(jù)top版本微調(diào)行號建議先用top -b -n 1 | head -20確認(rèn)你的輸出位置。3. 鎖定單個進(jìn)程查看指定進(jìn)程內(nèi)存占用的幾種方案標(biāo)題里的核心訴求是“查看某個進(jìn)程占用的系統(tǒng)內(nèi)存大小”。top雖然默認(rèn)顯示所有進(jìn)程但真正處理問題時我們往往只關(guān)心一兩個特定的進(jìn)程比如Java服務(wù)、Nginx、MySQL。這時候全量列表太吵最好精準(zhǔn)鎖定目標(biāo)。我列幾種我自己常用的做法。3.1 方案一找到PID后用top -p進(jìn)程ID實測最直接的方式先用pgrep或ps找到進(jìn)程的PID然后top -p只看這個進(jìn)程。pgrep -f nginx: worker top -p 12345top -p后面可以跟多個PID用逗號分隔比如top -p 12345,12346。這個模式下匯總區(qū)的負(fù)載、CPU、內(nèi)存信息還在下面的進(jìn)程列表只剩你指定的那一個。如果再配合-d 1把刷新間隔調(diào)成1秒基本就是一個“單進(jìn)程監(jiān)控面板”了。在多核CPU機器上如果這個進(jìn)程是多線程的你可能會困惑為什么這一行整體CPU占用率那么高——因為top默認(rèn)顯示的是進(jìn)程所有線程在物理核上的累計占用。想要更細(xì)的線程視角可以加-H參數(shù)這個后面單獨講。3.2 方案二在top交互界面里按條件過濾如果已經(jīng)在top的全量界面里了不想退出去再輸一遍命令可以用過濾功能精準(zhǔn)鎖定目標(biāo)。按o輸入過濾表達(dá)式例如COMMANDjava只顯示命令行里含java的進(jìn)程。也可以組合條件比如只看某個用戶的Java進(jìn)程USERroot再配合M鍵按內(nèi)存排序很快就能從一堆進(jìn)程里挑出目標(biāo)。如果只想看某個特定PIDtop默認(rèn)沒有“按PID顯示”的快捷鍵但可以用過濾表達(dá)式PID12345這是我平時最常用的方式。過濾時還能按等號精確匹配、按!排除功能相當(dāng)靈活。注意過濾表達(dá)式在輸入o之后出現(xiàn)在屏幕底部的行列里格式要嚴(yán)格按照字段值來。3.3 方案三按進(jìn)程名動態(tài)定位后進(jìn)入top有些場景下PID會隨時變化比如每次重啟服務(wù)PID都不同不適合寫死。這時可以用命令替換把pgrep找出來的PID直接傳給top。top -p $(pgrep -f your_service_name)如果匹配到多個PIDpgrep默認(rèn)每行一個需要轉(zhuǎn)成英文逗號分隔可以這樣top -p $(pgrep -f your_service_name | paste -sd,)我自己在調(diào)試復(fù)雜Java應(yīng)用時經(jīng)常這么干。輸入不算短但好處是即使應(yīng)用重啟只要命令行特征沒變執(zhí)行一次命令就能自動鎖到新PID上。3.4 補充思路用ps先排序再逐個確認(rèn)top適合動態(tài)觀察但如果你的目的是“立刻列出內(nèi)存占用最高的幾個進(jìn)程”ps配合排序更高效ps aux --sort-%mem | head -20這行命令會把內(nèi)存占用率最高的前20個進(jìn)程列出來。先全局掃一遍把懷疑對象的PID記住再進(jìn)top用-p做定點監(jiān)控效率最高。記住一個原則ps負(fù)責(zé)快速采樣top負(fù)責(zé)持續(xù)觀察兩個工具是互補關(guān)系不是替代關(guān)系。3.5 連續(xù)采樣判斷內(nèi)存趨勢與內(nèi)存泄漏單次看到某個進(jìn)程RES很高說明它當(dāng)前占得多但要判斷是不是內(nèi)存泄漏必須看趨勢。我遇到過一個典型場景一個Java服務(wù)跑著跑著RES從2GB漲到8GB業(yè)務(wù)量卻沒變化最后確認(rèn)是某個緩存列表不斷追加沒做清理。排查趨勢的土辦法就是連續(xù)采樣把每個時間點的RES記下來。for i in {1..60}; do top -b -n 2 -d 1 -p 12345 | awk $112345 {print strftime(%F %T), $0} | tail -1 sleep 5 done這段腳本的原理是top -b -n 2 -d 1 -p取第二幀數(shù)據(jù)管道交給awk過濾出目標(biāo)PID所在行再打上時間戳。跑幾分鐘后你就能看到某個進(jìn)程的RES曲線。如果它持續(xù)上漲且永遠(yuǎn)不回落內(nèi)存泄漏的嫌疑就非常大如果漲到一定程度趨于平穩(wěn)可能只是緩存池在預(yù)熱。4. 實戰(zhàn)場景CPU告警與內(nèi)存異常時的完整排查流程工具練完得上真戰(zhàn)場。這里我把一次典型的線上故障排查過程拆解出來你會看到top、ps、free、/proc是怎么配合使用真正把“某個進(jìn)程占了多少系統(tǒng)內(nèi)存”查清楚的。4.1 典型流程從全局到單進(jìn)程的五步定位法第一步先用free -h和uptime看整體。如果available內(nèi)存很低或者load average三個值都在飆升說明系統(tǒng)資源出狀況了。第二步打開top按M排序看是哪個進(jìn)程的RES沖在最前面按P排序看CPU占用又是誰最猛。很多時候這兩個排序結(jié)果不一致——比如MySQL內(nèi)存占第一但CPU高的是另一個查詢進(jìn)程這時你就要先把兩邊的依賴關(guān)系理出來。第三步找到嫌疑進(jìn)程的PID用top -p PID做定點監(jiān)控。第四步結(jié)合/proc/PID/status等文件看進(jìn)程內(nèi)部細(xì)節(jié)。第五步根據(jù)結(jié)論處理——重啟服務(wù)、調(diào)整配置、優(yōu)化代碼或者只是臨時用renice降一下優(yōu)先級給核心業(yè)務(wù)騰資源。這套流程核心就一個思想先全局、后局部先現(xiàn)象、后原因。誰也不會一上來就盯著某個進(jìn)程看半天全局排序能幫你迅速縮小懷疑范圍。4.2 用/proc深入進(jìn)程內(nèi)部看內(nèi)存細(xì)節(jié)top提供了“進(jìn)程占了多少內(nèi)存”的結(jié)論但進(jìn)程內(nèi)部到底是哪塊內(nèi)存吃得最多top說不清楚。這時就要看/proc文件系統(tǒng)這是Linux內(nèi)核暴露給用戶態(tài)的“進(jìn)程檔案柜”。grep -E VmRSS|VmSize|Threads /proc/12345/statusVmRSS就是和top里RES對應(yīng)的物理內(nèi)存大小單位是KBVmSize對應(yīng)VIRTThreads是線程數(shù)。想看更細(xì)的內(nèi)存區(qū)域分布用pmappmap -x 12345pmap會把進(jìn)程地址空間里的每一段內(nèi)存映射列出來比如堆heap、棧stack、共享庫、匿名映射等每段占多大都能看到。這個命令在定位“進(jìn)程為什么內(nèi)存高”時特別有用能看出是堆外內(nèi)存失控還是某個共享庫映射了超大文件還是線程棧累積太多導(dǎo)致的虛擬內(nèi)存膨脹。另外還有一個很實用的檢查看進(jìn)程打開了多少文件描述符。ls /proc/12345/fd | wc -l文件描述符泄漏同樣會導(dǎo)致內(nèi)存異常特別是連接型服務(wù)。我曾經(jīng)見過一個API網(wǎng)關(guān)進(jìn)程因為連接池沒釋放fd數(shù)量一路飆升到幾萬個RES也同步漲上去最后靠觀察fd數(shù)量變化才定位到根因。4.3 監(jiān)控腳本實例自動記錄某個進(jìn)程的資源占用如果總不能讓值班同事手動刷新top那就寫個簡單的監(jiān)控腳本把數(shù)據(jù)自動落盤。下面是我用過的一個精簡版適合跟蹤單個進(jìn)程#!/bin/bash # 監(jiān)控指定進(jìn)程的CPU和內(nèi)存占用追加寫入日志 PID$1 LOG${2:-/tmp/top_monitor.log} echo time_pid_cpu%_mem%_res_kb_virt_kb $LOG while true; do top -b -n 2 -d 1 -p $PID | awk -v pid$PID $1 pid { printf %s %d %s %s %s %s\n, strftime(%F %T), $1, $9, $10, $6, $5 exit } $LOG sleep 5 done運行方式chmod x monitor_proc.sh ./monitor_proc.sh 12345 /tmp/my_service.log原理沒有太高深的地方核心調(diào)用top -b -n 2 -d 1 -p然后用awk精確匹配PID行提取第9列CPU%、第10列MEM%、第6列RESKB、第5列VIRTKB打時間戳后追加寫入文件。腳本留了5秒間隔避免日志漲太快。需要停止時用CtrlC或者配合nohup放后臺長期跑。這個腳本價值在于等問題復(fù)現(xiàn)時你手里有一份時間線完整的數(shù)據(jù)文件能直接畫出內(nèi)存趨勢圖比事后猜測強太多。4.4 線程級視角用top -H看到進(jìn)程內(nèi)部的線程top默認(rèn)顯示進(jìn)程維度但某些場景下進(jìn)程整體CPU不高問題出在某個線程上。尤其是Java這種多線程應(yīng)用一個進(jìn)程可能開了幾十個線程某個線程死循環(huán)燒核從進(jìn)程維度看CPU總和高但不知道具體是哪個線程。這時用top -H -p PIDtop會把該進(jìn)程的所有線程逐條列出來每行是一個線程PID列顯示的是TID線程ID。先找到CPU占用高的那個線程ID再將它轉(zhuǎn)成十六進(jìn)制printf %x\n 12345然后拿到該線程的堆?;蛘叻治鋈罩揪湍芫_定位到代碼層面。這個方法我在排查Java應(yīng)用線程死循環(huán)、線程阻塞等問題時用過很多次效率極高。同樣top -H也支持-b批處理模式可以腳本化采集線程級數(shù)據(jù)。5. 踩坑記錄與常見問題速查工具本身不難難的是輸出結(jié)果的解讀。我把這幾年用過top之后踩過的坑、誤判過的數(shù)據(jù)總結(jié)出來希望你看完能避開這些習(xí)慣性誤區(qū)。5.1 top結(jié)果顯示的內(nèi)存不等于真實物理內(nèi)存占用最典型的誤解是看到某個進(jìn)程VIRT十幾個GB就斷定“服務(wù)器內(nèi)存爆了”。VIRT包含的是虛擬地址空間進(jìn)程申請過的虛擬內(nèi)存不一定全部映射到物理內(nèi)存。Java進(jìn)程的堆即使預(yù)分配了8GB實際RES可能只有2GB因為未使用部分的物理頁根本沒有分配。判斷物理內(nèi)存占用永遠(yuǎn)以RES和%MEM為主要依據(jù)。另一個坑是SHR共享內(nèi)存重復(fù)計算。用top把所有進(jìn)程的RES加總想和物理內(nèi)存總量做對比你會發(fā)現(xiàn)數(shù)值“超了”。原因就是共享庫、共享內(nèi)存段被多個進(jìn)程分別計入自己的RES里。所以我不建議用“進(jìn)程RES求和”的方式來核對系統(tǒng)內(nèi)存總量它只能用來大致比較進(jìn)程之間的相對占用大小。5.2 free里available和used為什么對不上很多新手看到free輸出used很高就慌了結(jié)果系統(tǒng)其實一切正常。原因是Linux會把空閑內(nèi)存盡量用作文件緩存cache這是內(nèi)核主動利用閑置內(nèi)存提升IO性能的行為不屬于“不可回收占用”。判斷內(nèi)存是否緊張應(yīng)該看available這一列它表示“在不觸發(fā)明顯swap的前提下還能分配給新進(jìn)程多少內(nèi)存”比free列更有參考價值。結(jié)合起來用top時如果全進(jìn)程RES加起來并不高但free顯示used很高很可能是cache在漲而不是哪個業(yè)務(wù)進(jìn)程真的在“泄漏”。這種情況通常是大量文件讀寫導(dǎo)致的頁緩存增長系統(tǒng)壓力大時內(nèi)核會自動回收一般不需要人工干預(yù)。5.3 常見問題速查表遇到下面這些場景時可以直接對照排查方向?,F(xiàn)象可能原因快速排查命令處理方向CPU整體跑滿但進(jìn)程列表看不到單一高占用進(jìn)程大量短生命周期進(jìn)程或頻繁上下文切換top -H、vmstat 1檢查短進(jìn)程來源限制并發(fā)Java進(jìn)程RES持續(xù)上漲不回落堆內(nèi)存或堆外內(nèi)存泄漏jmap -heap、pmap -x抓堆轉(zhuǎn)儲定位引用泄漏內(nèi)存很高但free available很低頁緩存占用或確實內(nèi)存緊張cat /proc/meminfo觀察cache、swap變化趨勢磁盤IO等待wa特別高進(jìn)程在瘋狂讀寫磁盤而非計算密集iotop、top里按左箭頭切到IO列排查具體IO讀寫進(jìn)程進(jìn)程列表中RES和VIRT都很大可能是大文件mmap或堆預(yù)分配pmap -x PID確認(rèn)哪段映射最大5.4 最后再分享兩個實戰(zhàn)小技巧第一個技巧top的-d可以控制刷新間隔但設(shè)置太小比如0.1秒反而容易讓數(shù)據(jù)波動劇烈一般生產(chǎn)環(huán)境用-d 2或-d 3比較合理。如果機器核數(shù)特別多我還會配合排序快捷鍵快速找到真正出問題的進(jìn)程。第二個技巧如果遇到“進(jìn)程明明還在但top只有CPU高、RES不高”的情況要多考慮是不是短連接風(fēng)暴或者挖礦腳本反復(fù)拉起新進(jìn)程。top里不斷出現(xiàn)陌生進(jìn)程名并很快消失就要懷疑是定時任務(wù)或惡意腳本在作怪crontab -l、檢查/tmp目錄下的可執(zhí)行文件都值得做一遍。我的個人習(xí)慣是把top當(dāng)作第一手的現(xiàn)場工具遇到任何系統(tǒng)異常都先進(jìn)top看一眼但等到了深挖階段一定把ps、free、/proc、pmap這些工具串起來綜合判斷。工具雖小組合起來才是完整的排查體系。