關於進程內存使用的一點學習和實踐


在測試,特別是性能測試或者系統的穩定性測試中,內存的使用情況是一個很重要的監控點,不管是從資源使用的角度還是從發現內存泄露問題的角度。

如果籠統的來看,大概就是兩個指標,系統的內存使用率和進程使用的內存。但是現實世界的事情往往沒有那麼簡單,稍微細一點來看其實有很多的科目。本文不是一個全面的關於內存使用的探討,甚至也不是一個詳細的Linux下面進程內存使用情況的分析,儘管這裏的實踐是基於此的。


這裏想做的是稍微細節一點的來看Linux下一個進程的內存使用情況,包括棧和堆。

首先我們從一個簡單的C程序開始。且慢,先說一下我試驗的環境。
platform:  CentOS release 5.6 (Final)  Linux localhost.localdomain 2.6.18-238.19.1.el5xen #1 SMP Fri Jul 15 08:57:45 EDT 2011 i686 i686 i386 GNU/Linux

gcc version 4.1.2 20080704 (Red Hat 4.1.2-50)  


[root@localhost test]# cat simple_hello.c         
#include <stdio.h>                                
                                                 
int main()                                        
{                                                 
 int i,m = 1024, n = 0, x;                      
 int a[m];                                       
                                                 
 printf("assign %d values to a[%d]...\n", n, m);
                                                 
 for (i = 0; i < n; i++)                         
 {                                               
   a[i] = 100;                                   
 }                                               
                                                 
 printf("value assigned.\n");                                                               
 scanf("%d", &x);  /* to hold program.. */       
 return 0;                                       
}                                                 

真是一個很簡單的程序,只比hello world複雜一點點。創建一個靜態的數組,長度通過m來控制,然後選擇性的給部分或者全部的元素賦值,通過n來控制。好吧,這個一個簡單的程序能看出什麼呢?那我們一起來看看。

在Linux下面,查看一個進程的內存使用我們可以下面的命令來實現,只需把其中的[pid]換成進程實際的pid。
# cat /proc/[pid]/status
爲了方便,我們把查找pid和看內存整合成一條命令,後面這將是我們唯一的測試工具。
cat /proc/`ps -ef|grep hello | grep -v grep | awk '{print $2}'`/status | grep -E 'VmSize|VmRSS|VmData|VmStk|VmExe|VmLib'

在這裏我們關注VmSize|VmRSS|VmData|VmStk|VmExe|VmLib 這個6個指標,下面有一些簡單的解釋。
VmSize(KB) :虛擬內存大小。整個進程使用虛擬內存大小,是VmLib, VmExe, VmData, 和 VmStk的總和。
VmRSS(KB):虛擬內存駐留集合大小。這是駐留在物理內存的一部分。它沒有交換到硬盤。它包括代碼,數據和棧。
VmData(KB): 程序數據段的大小(所佔虛擬內存的大小),堆使用的虛擬內存。
VmStk(KB): 任務在用戶態的棧的大小,棧使用的虛擬內存

VmExe(KB): 程序所擁有的可執行虛擬內存的大小,代碼段,不包括任務使用的庫

VmLib(KB) :被映像到任務的虛擬內存空間的庫的大小



Ok, 測試開始了。

首先,我們固定m的值爲409600,相當於400K,因爲數組的元素是int型,在我的環境裏面是4Byte,所以真個數組的大小爲1600KB。

m固定化,我們不斷調整n的大小,重寫編譯,執行,然後用上面的命令查看內存的使用情況,這樣我們得到了下面這個表格。

從這裏我們可以得到幾個信息:

1. 靜態的數組使用的空間被分配到VmStk,也就是棧區。

2. 在數組沒有初始化的時候並沒有實際佔用虛擬內存,看VmRss,但是整個虛擬內存的大小還是分配了,VmSize。



接下來我們做另一個測試,讓n=m,調整m的大小,也就是說調整數組的大小,然後初始化所有的元素。

這樣我們得到了下面的表。




從這個表中,我們可以看出:

1. 棧的使用確實和數組的size相關,但是有個起始預分配的大小,應該是編譯器的優化。

2. VmRSS和VmSize跟着一起在漲。


嗯,是跟着在漲,但是有個問題,棧的空間是有限的,通過這個程序或者你查看系統的設置你可以找到上限。在我的這臺機器上上限是8MB,每個進程,所以這裏如果m的值大於2048000,就會出segmentation fault的錯誤。當然你也可以調整系統的設置,比如通過

# ulimit -s 10240
將上限調爲10MB。但是這個終究不能調得很大,因爲對系統會有影響。所以編程中太大的靜態數組不是有個好主意。


棧的大小限制還是蠻嚴格的,好吧,那我們來看看程序可以使用的另一類存儲空間,堆(heap)。關於堆和棧的區別可能是一個常被問道的問題,你在很多地方可以找到答案。

OK,我們繼續我們的實驗,考慮到現在很多系統的後臺用C++來寫,我們也把測試程序換成C++的。好吧,我承認其實沒有太大的區別,只是申請內存的方式不太一樣了。

[root@localhost test]# cat hello.cpp                   
#include <iostream>                                    
using namespace std;                                   
                                                      
int main()                                             
{                                                      
 cout<<"New some space for array, assign value"<<endl;
                                                      
 intm = 409600, n = 409600;                          
 int *p = new int[m];                                 
                                                      
 for (int i = 0; i < n; i++)                          
 {                                                    
   p[i] = 100;                                        
 }                                                    
                                                      
 cout<<"value assigned."<<endl;                       
                                                      
 int x;                                               
 cin>>x;  //hold program                              
}

這個我們使用的是動態的數組,也就是說數組的內容空間是我們顯式的通過new通過向系統申請的。測試工具還是上面的命令行。

延遲我們的風格,首先固定m的值,這裏是409600,400K,然後調整n的值,看情況是怎樣的。


一些觀察的結果:

1. VmData的大小約爲1600KB,因爲每個元素4Byte,系統還有一些別的使用。

2. n控制有多少數組的元素被初始化,這也影響了VmRSS的大小。

整個VmSize的大小並不受初始化範圍的影響,這個結果和之前棧的實驗中看到的現象很類似,只不過這裏換成了VmData。


接下來我們讓n=m,然後兩個一起調整。



可以看出:

1. VmData的size在增長,VmRSS也在跟着一起增長。但是VmRSS一開始分配的餘量就比較大,所以VmData剛開始的增長並未立即導致VmSize的改變。

2. VmSize也跟着一起增長,應該的。


請注意這裏發生了一些很奇怪的現象,那就是當m=40960的時候,你會發現VmData的值比m=20480的時候還要小,很不正常。

實驗了很多次,我發現在一個特定的區間裏面VmData並未按照m的值增長,而且下降了,然後又開始增長。和同事一起check了一下,目前的解釋是裝載程序做了一些tricky的事情,也可能是某種優化,具體的還不是很清楚。


在實際的產品代碼,特別是後臺的Linux服務器程序中,通常會大量的申請和釋放內存,動態的,使用的就是我們這裏提到的VmData,堆上的內存。Ok,你知道了,我要說的是memory leak的問題。通過觀察VmData和VmRSS,我們能夠很明確的察覺內存泄露的問題。



前面在分析棧的時候我們提到了系統對棧的大小有上限,比如我的系統默認是8MB。那麼有個問題就是,那麼堆呢?

嗯,這個部分其實就涉及到操作系統的內存管理的策略和方法,是個很大的問題,推薦看一下《深入理解計算機系統》相關的章節或者關於現代的操作系統的書籍。這裏我們簡單做了一下實驗。


我的實驗機器的內存是1GB,一個Linux的虛擬機。


當我們設置 m = n = 204800000的時候,相當於要申請800MB的內存。

我們產品status的時候發現VmData > VmRSS, 這在前面m=n的情況下從來沒有出現過。

VmSize:   802784 kB

VmLck:         0 kB

VmHWM:    684384 kB

VmRSS:    676808 kB

VmData:   800064 kB


所以一定是有些事情發生變化了。於是我們看了一下系統的內存,以及SWAP的使用情況。
 Mem:    900096k total,   893180k used,     6916k free,     1012k buffers
 Swap:  2096472k total,   147320k used,  1949152k free,    33012k cached
發現系統的物理內存已經快用了,還留了一些給系統,然後開始使用SWAP了,大家知道這裏的SWAP其實是磁盤文件。
在這種情況下,我們也許可以更容易理解駐留內存的意義,以及爲什麼內存不夠會導致性能的明顯下降。

如果進一步把m=n加到300M,也是申請1.2GB的內存(已經大於所有物理內存的數目),可能會更明顯。

VmSize:  1202784 kB

VmLck:         0 kB

VmHWM:    814064 kB

VmRSS:    793588 kB

VmData:  1200064 kB


Mem:    900096k total,   894128k used,     5968k free,     1104k buffers

Swap:  2096472k total,   541812k used,  1554660k free,    13616k cached


通過這幾個小的例子,會發現就是內存使用這樣一個指標其實背後都會有很多值得去了解和探討的細節,其實這裏談到的也只是冰山一角。只有瞭解並理解了這些重要的細節,我們在測試中去評估我們的產品的時候纔會更加的準備,更容易發現和定位問題。另外不要忘記實際中的產品比這兩個豆腐塊程序要複雜得多。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章