值得收藏的查詢進程佔用內存情況方法匯總
- 2020 年 2 月 14 日
- 筆記
| 作者:楊一迪,騰訊雲數據庫後台開發工程師,主要負責騰訊雲PostgreSQL、CynosDB等產品後台開發工作。
現網運維過程中,常有用戶諮詢實例的內存使用情況,故而和大家一起分享我對於內存佔用情況的理解,共同進步。
1
簡述
查看進程佔用內存情況的方式比較多,包括top命令、/proc/${pid}/smaps文件統計、cgroup統計等。但不同方式的查詢結果具體代表什麼含義,這裡通過一個測試程序,簡單驗證下這三種查詢方式如何反映進程的內存使用情況。想看結論的直接看文末的總結。本文有任何錯誤,歡迎在留言區討論指導。
1
測試程序
為了驗證進程的私有內存、共享內存使用情況,寫了個簡單的http server,主要代碼如下。
1. 申請私有內存
申請一個指定大小的數組,其中g_str為全局變量,不會在接口返回時銷毀。
func expandGlobalVar(writer http.ResponseWriter, request *http.Request) { type Request struct { Length int } data, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf("ioutil.ReadAll failed. err: %v", err) writer.Write([]byte("io failed")) return } req := &Request{} json.Unmarshal(data, req) g_str = make([]byte, req.Length) for i:=0;i<req.Length;i++{ g_str[i]='a' } curLength, curCap := len(g_str), cap(g_str) writer.Write([]byte(fmt.Sprintf("req length: %d, length: %d, cap: %d", req.Length, curLength, curCap))) return}
2. 掛載共享內存文件
僅掛載共享內存文件,還未讀取共享內存,此時並沒有申請共享內存。
func mmapAttach(writer http.ResponseWriter, request *http.Request) { data, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf("ioutil.ReadAll failed. err: %v", err) writer.Write([]byte("io failed")) return } type Request struct { Filename string } req := &Request{} json.Unmarshal(data, req) mmapsFile, err = mmap.Open(req.Filename) if err != nil { writer.Write([]byte(err.Error())) } return}
3. 讀取共享內存
讀取指定長度的共享內存文件,此時會申請共享內存。
func mmapRead(writer http.ResponseWriter, request *http.Request) { data, err := ioutil.ReadAll(request.Body) if err != nil { log.Printf("ioutil.ReadAll failed. err: %v", err) writer.Write([]byte("io failed")) return } type Request struct { Start int64 Length int64 } req := &Request{} json.Unmarshal(data, req) buf := make([]byte, req.Length) length, err := mmapsFile.ReadAt(buf, req.Start) if err != nil { log.Printf("readat error. err: ", err) writer.Write([]byte("readat error")) return } log.Printf("length: %d", length) return}
4. 測試步驟
1)啟動2個http server
~/code/httpMock/bin/httpMock -p 1001 &~/code/httpMock/bin/httpMock -p 1002 &
2)分別申請50M的私有內存
curl -d '{"Length":50000000}' http://127.0.0.1:1001/expandGlobalVarcurl -d '{"Length":50000000}' http://127.0.0.1:1002/expandGlobalVar
3)分別申請100M、200M的共享內存,其中有100M由進程共享
curl -d '{"Filename":"/root/code/httpMock/mmap_files/log"}' http://127.0.0.1:1001/mmapAttachcurl -d '{"Filename":"/root/code/httpMock/mmap_files/log"}' http://127.0.0.1:1002/mmapAttachcurl -d '{"Start": 0, "Length":100000000}' http://127.0.0.1:1001/mmapReadcurl -d '{"Start": 0, "Length":200000000}' http://127.0.0.1:1002/mmapRead
1
測試結果
1. /proc/${pid}/smaps
smaps文件記錄了進程中各個內存段的使用情況,按照上述測試步驟,可觀察到smaps中的內存變化情況如下:
1)啟動http server後,Rss佔用3M左右

2)申請50M的私有內存後,可以看到私有內存所在的內存段,Rss/Pss分別佔用50M左右


3)分別申請100M、200M的共享內存,其中有100M由進程共享。申請後私有內存段擴充到100M,Rss增加量=私有內存增加量+共享內存增加量,Pss=私有內存+共享內存/共享進程數。



結論:smaps中記錄了進程的各個內存段,其中Rss=私有內存+共享內存,Pss=私有內存+共享內存/共享進程數,Rss中的共享內存會被重複計算。
2. top命令
top命令返回了物理內存和共享內存的使用情況,按上述測試步驟,可觀察到top命令結果變化如下:
1)啟動2個http server後,RES私有內存佔用3M左右,與smaps的RSS一致:

2)分別申請50M的私有內存,RES擴充到50M左右:

3)分別申請100M、200M的共享內存後,RES與smaps中的Rss類似,擴充了150M和250M左右,SHR擴充了100M和200M:

結論:top命令結果中,RES代表私有內存+共享內存,SHR代表共享內存,單位都為KB。top命令的RES與smaps中的RSS基本一致
3. cgroup memory子系統
cgroup memory子系統中,memory.usage_in_bytes記錄了cgroup組中的進程的內存使用情況,memory.stat記錄了各類內存的詳細使用情況,按上述測試步驟,可觀察到cgroup統計結果變化如下:
1)在同一cgroup組中啟動http server,注意需要通過cgexec啟動,保證進程啟動時就在cgroup組中。啟動後rss為2M左右,等於(3M-2M)2,即私有內存量進程數,與top命令、smaps計算的私有內存量基本一致:
cgdelete memory:httpMockcgcreate -g memroy:httpMockcgexec -g memory:httpMock ~/code/httpMock/bin/httpMock -p 1001 &cgexec -g memory:httpMock ~/code/httpMock/bin/httpMock -p 1002 &

2)分別申請50M的私有內存後,兩進程共擴充100M左右:

3)分別申請100M、200M的共享內存後,內存使用量與top命令和smaps中統計的私有內存用量基本一致:

結論:cgroup中的memory.usage_in_bytes和memory.stat的rss字段,統計的是進程的私有內存
4. cgroup的內存限制與page cache
當系統讀取文件時,會在系統緩存中緩存文件內容,以減少硬盤IO。這部分內存緩存,會統計到cgroup.stat中的cache字段。而在多個cgroup組都有讀取相同文件時,這部分緩存只會統計到第一個讀該文件的cgroup組中。經過驗證,這部分緩存不會觸發oom,在緩存+內存佔用達到內存限制時,會回收系統緩存。驗證過程如下:
1)啟動http server後,加載共享文件並讀取,可看到佔用了100M的cache:

2)調整內存上限,使其低於cache+rss,觸發了緩存回收:

[ 調整內存上限前,系統buf+cache為509M ]

[ 調整上限後觸發緩存回收 ]
3)嘗試將內存上限調整到已使用內存以下,調整失敗:

1
總結
1)smaps中記錄了進程佔用的各個內存段,每個內存段中的Rss表示私有內存+共享內存大小,其中共享內存被多個進程佔用時會被重複計算; 2)smaps中的Pss會將共享內存部分按共享進程數進行均攤,Pss表示私有內存+共享內存/共享進程數,因此計算一組進程佔用的內存總數時,累加Pss的結果更準確; 3)smaps中的Shared_Clean/Shared_dirty表示共享內存大小 4)top命令的RES表示私有內存+共享內存大小,單位為KB,其中共享內存被多個進程佔用時會被重複計算; 5)top命令的SHR表示共享內存大小,單位為KB; 6)cgroup的memory.stat中cache表示系統page cache大小,在進程讀取文件時,文件會緩存到系統內存,這部分緩存的內存就會記到cache中; 7)cgroup的memory.stat中rss表示私有內存大小,不包括共享內存部分; 8)cgroup的memroy.usage_in_bytes表示內存使用量,主要包括memory.stat的cache和rss; 9)cgroup的內存限制,主要限制rss大小,當rss+cache>內存上限時會優先觸發cache的回收。
綜上所述,當我們考慮進程的內存使用量時,如果關注是否會觸發oom,則主要看memory.stat的rss部分即可,但rss並不能反映共享內存的使用情況;如果要關注進程的私有內存+共享內存佔用情況,則可以主要看smaps中的Pss。
參考資料:
cgroup:https://www.kernel.org/doc/Documentation/cgroup-v1/memory.txt linux /proc/pid/smaps各字段含義:https://blog.csdn.net/u010902721/article/details/46446031
往期推薦

(點擊圖片即可跳轉閱讀)

開年大禮包

↓↓更多驚喜優惠請點這兒~