在測試,特別是性能測試或者系統的穩定性測試中,內存的使用情況是一個很重要的監控點,不管是從資源使用的角度還是從發現內存泄露問題的角度。
如果籠統的來看,大概就是兩個指標,系統的內存使用率和進程使用的內存。但是現實世界的事情往往沒有那麼簡單,稍微細一點來看其實有很多的科目。本文不是一個全面的關於內存使用的探討,甚至也不是一個詳細的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