天天看點

如何觀察程序的記憶體占用情況

<b>概述</b>

<b></b>      想必在linux上寫過程式的同學都有分析程序占用多少記憶體的經曆,或者被問到這樣的問題——你的程式在運作時占用了多少記憶體(實體記憶體)?通常我們可以通過top指令檢視程序占用了多少記憶體。這裡我們可以看到virt、res和shr三個重要的名額,他們分别代表什麼意思呢?這是本文需要跟大家一起探讨的問題。當然如果更加深入一點,你可能會問程序所占用的那些實體記憶體都用在了哪些地方?這時候top指令可能不能給到你你所想要的答案了,不過我們可以分析proc檔案系統提供的smaps檔案,這個檔案詳盡地列出了目前程序所占用實體記憶體的使用情況。

這篇blog總共分為三個部分。第一部分簡要闡述虛拟記憶體和駐留記憶體這兩個重要的概念;第二部分解釋top指令中virt、res以及shr三個參數的實際參考意義;最後一部分向大家介紹一下smaps檔案的格式,通過分析smaps檔案我們可以詳細了解程序實體記憶體的使用情況,比如mmap檔案占用了多少空間、動态記憶體開辟消耗了多少空間、函數調用棧消耗了多少空間等等。

<b>關于記憶體的兩個概念</b>

要了解top指令關于記憶體使用情況的輸出,我們必須首先搞清楚<b>虛拟記憶體</b>(virtual memory)和<b>駐留記憶體</b>(resident memory)兩個概念。

<b>【虛拟記憶體】</b>

首先需要強調的是虛拟記憶體不同于實體記憶體,雖然兩者都包含記憶體字眼但是它們屬于兩個不同層面的概念。程序占用虛拟記憶體空間大并非意味着程式的實體記憶體也一定占用很大。虛拟記憶體是作業系統核心為了對程序位址空間進行管理(process address space management)而精心設計的一個邏輯意義上的記憶體空間概念。我們程式中的指針其實都是這個虛拟記憶體空間中的位址。比如我們在寫完一段c++程式之後都需要采用g++進行編譯,這時候編譯器采用的位址其實就是虛拟記憶體空間的位址。因為這時候程式還沒有運作,何談實體記憶體空間位址?<b>凡是程式運作過程中可能需要用到的指令或者資料都必須在虛拟記憶體空間中</b>。既然說虛拟記憶體是一個邏輯意義上(假象的)的記憶體空間,為了能夠讓程式在實體機器上運作,那麼必須有一套機制可以讓這些假象的虛拟記憶體空間映射到實體記憶體空間(實實在在的ram記憶體條上的空間)。這其實就是作業系統中頁映射表(page table)所做的事情了。<b>核心會為系統中每一個程序維護一份互相獨立的頁映射表</b>。。頁映射表的基本原理是将程式運作過程中需要通路的一段虛拟記憶體空間通過頁映射表映射到一段實體記憶體空間上,這樣cpu通路對應虛拟記憶體位址的時候就可以通過這種查找頁映射表的機制通路實體記憶體上的某個對應的位址。“頁(page)”是虛拟記憶體空間向實體記憶體空間映射的基本單元。

      下圖1示範了虛拟記憶體空間和實體記憶體空間的互相關系,它們通過page table關聯起來。其中虛拟記憶體空間中着色的部分分别被映射到實體記憶體空間對應相同着色的部分。而虛拟記憶體空間中灰色的部分表示在實體記憶體空間中沒有與之對應的部分,也就是說灰色部分沒有被映射到實體記憶體空間中。這麼做也是本着“按需映射”的指導思想,因為虛拟記憶體空間很大,可能其中很多部分在一次程式運作過程中根本不需要通路,是以也就沒有必要将虛拟記憶體空間中的這些部分映射到實體記憶體空間上。

到這裡為止已經基本闡述了什麼是虛拟記憶體了。總結一下就是,虛拟記憶體是一個假象的記憶體空間,在程式運作過程中虛拟記憶體空間中需要被通路的部分會被映射到實體記憶體空間中。虛拟記憶體空間大隻能表示程式運作過程中可通路的空間比較大,不代表實體記憶體空間占用也大。

如何觀察程式的記憶體占用情況

&amp;lt;img class="alignnone size-full wp-image-153" alt="t1" src="http://www.bo56.com/wp-content/uploads/2013/08/t1.png" width="500" height="251" /&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;

【<b>駐留記憶體】</b>

駐留記憶體,顧名思義是指那些被映射到程序虛拟記憶體空間的實體記憶體。上圖1中,在系統實體記憶體空間中被着色的部分都是駐留記憶體。比如,a1、a2、a3和a4是程序a的駐留記憶體;b1、b2和b3是程序b的駐留記憶體。程序的駐留記憶體就是程序實實在在占用的實體記憶體。一般我們所講的程序占用了多少記憶體,其實就是說的占用了多少駐留記憶體而不是多少虛拟記憶體。因為虛拟記憶體大并不意味着占用的實體記憶體大。

關于虛拟記憶體和駐留記憶體這兩個概念我們說到這裡。下面一部分我們來看看top指令中virt、res和shr分别代表什麼意思。

top指令中virt、res和shr的含義

      搞清楚了虛拟記憶體的概念之後解釋virt的含義就很簡單了。virt表示的是程序虛拟記憶體空間大小。對應到圖1中的程序a來說就是a1、a2、a3、a4以及灰色部分所有空間的總和。也就是說virt包含了在已經映射到實體記憶體空間的部分和尚未映射到實體記憶體空間的部分總和。

res的含義是指程序虛拟記憶體空間中已經映射到實體記憶體空間的那部分的大小。對應到圖1中的程序a來說就是a1、a2、a3以及a4幾個部分空間的總和。是以說,看程序在運作過程中占用了多少記憶體應該看res的值而不是virt的值。

最後來看看shr所表示的含義。shr是share(共享)的縮寫,它表示的是程序占用的共享記憶體大小。在上圖1中我們看到程序a虛拟記憶體空間中的a4和程序b虛拟記憶體空間中的b3都映射到了實體記憶體空間的a4/b3部分。咋一看很奇怪。為什麼會出現這樣的情況呢?其實我們寫的程式會依賴于很多外部的動态庫(.so),比如libc.so、libld.so等等。這些動态庫在記憶體中僅僅會儲存/映射一份,如果某個程序運作時需要這個動态庫,那麼動态加載器會将這塊記憶體映射到對應程序的虛拟記憶體空間中。多個進展之間通過共享記憶體的方式互相通信也會出現這樣的情況。這麼一來,就會出現不同程序的虛拟記憶體空間會映射到相同的實體記憶體空間。這部分實體記憶體空間其實是被多個程序所共享的,是以我們将他們稱為共享記憶體,用shr來表示。某個程序占用的記憶體除了和别的程序共享的記憶體之外就是自己的獨占記憶體了。是以要計算程序獨占記憶體的大小隻要用res的值減去shr值即可。

<b>程序的</b><b>smaps</b><b>檔案</b><b></b>

<b>檢視指令是:cat /proc/程序的pid/smaps</b>

通過top指令我們已經能看出程序的虛拟空間大小(virt)、占用的實體記憶體(res)以及和其他程序共享的記憶體(shr)。但是僅此而已,如果我想知道如下問題:

程序的虛拟記憶體空間的分布情況,比如heap占用了多少空間、檔案映射(mmap)占用了多少空間、stack占用了多少空間?

 程序是否有被交換到swap空間的記憶體,如果有,被交換出去的大小?

mmap方式打開的資料檔案有多少頁在記憶體中是髒頁(dirty page)沒有被寫回到磁盤的?

mmap方式打開的資料檔案目前有多少頁面已經在記憶體中,有多少頁面還在磁盤中沒有加載到page cahe中?

等等

以上這些問題都無法通過top指令給出答案,但是有時候這些問題正是我們在對程式進行性能瓶頸分析和優化時所需要回答的問題。所幸的是,世界上解決問題的方法總比問題本身要多得多。linux通過proc檔案系統為每個程序都提供了一個smaps檔案,通過分析該檔案我們就可以一一回答以上提出的問題。

接下來8個字段的含義分别如下:

<b>size</b><b>:</b>表示該映射區域在虛拟記憶體空間中的大小。

<b>rss:</b>表示該映射區域目前在實體記憶體中占用了多少空間。

<b>shared_clean:</b>和其他程序共享的未被改寫的page的大小。

<b>shared_dirty: </b>和其他程序共享的被改寫的page的大小。

<b>private_clean:</b>未被改寫的私有頁面的大小。

<b>swap:</b>表示非mmap記憶體(也叫anonymous memory,比如malloc動态配置設定出來的記憶體)由于實體記憶體不足被swap到交換空間的大小。

如何觀察程式的記憶體占用情況

圖2. smaps檔案示例

有了smap如此詳細關于虛拟記憶體空間到實體記憶體空間的映射資訊,相信大家已經能夠通過分析該檔案回答上面提出的4個問題。

最後希望所有讀者能夠通過閱讀本文對程序的虛拟記憶體和實體記憶體有一個更加清晰認識,并能更加準确了解top指令關于記憶體的輸出,最後可以通過smaps檔案更進一步分析程序使用記憶體的情況。