一個由C/C++編譯的程序占用的內存分為以下幾個部分 :
1、棧區(stack):由編譯器自動分配釋放 ,存放函數調用函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。
2、堆區(heap):一般由程序員分配釋放,如malloc 來分配的全局指針。若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式倒是類似于鏈表,呵呵。
3、全局區(靜態區)(static):全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量在相鄰的另一塊區域。程序結束后有系統釋放。?
4、文字常量區 :常量字符串就是放在這里的。 程序結束后由系統釋放。?
5、程序代碼區:存放函數體的二進制代碼。
- 進程內存分布總結如下:
- - 程序段 (Text Segment):可執行文件代碼的內存映射
- 程序代碼在內存中的映射,存放函數體的二進制代碼。
- 可執行代碼、字符串字面值、只讀變量
- - 數據段 (Data Segment):可執行文件的已初始化全局變量的內存映射
- 在程序運行初已經對變量進行初始化的數據。
- 已初始化且初值非0的全局變量和局部靜態變量,全局靜態變量,常量
- - BSS段 (BSS Segment):未初始化的全局變量或者靜態變量(用零頁初始化)
- 在程序運行初未對變量進行初始化的數據。
- 未初始化或初值為0的全局變量和靜態局部變量
- - 堆區 (Heap) : 存儲動態內存分配,匿名的內存映射
- 存儲動態內存分配,需要程序員手工分配,手工釋放.
- 注意它與數據結構中的堆是兩回事,分配方式類似于鏈表
- - 棧區 (Stack) : 進程用戶空間棧,由編譯器自動分配釋放,存放函數的參數值、局部變量的值等
- 存儲局部、臨時變量,函數參數
- 函數調用時,存儲函數的返回指針,用于控制函數的調用和返回。
- 在程序塊開始時自動分配內存,結束時自動釋放內存,其操作方式類似于數據結構中的棧。
- 但不包括static聲明的變量, static 意味著 在“數據段”中 存放變量
- - 映射段(Memory Mapping Segment):任何內存映射文件
- 內核將文件的內容直接映射到內存
- 內存映射是一種方便高效的文件I/O方式,所以它被用來加載動態庫。創建一個不對應于任何文件的匿名內存映射也是可能的,此方法用于存放程序的數據。
- 該區域用于映射可執行文件用到的動態鏈接庫。
?????????Linux 對進程地址空間有個標準布局,地址空間中由各個不同的內存段組成 (Memory Segment),主要的內存段如下:圖示如下:
????????Linux 內核將這 4G 字節的空間分為兩部分,將最高的 1G 字節(0xC0000000-0xFFFFFFFF)供內核使用,稱為 內核空間。而將較低的3G字節(0x00000000-0xBFFFFFFF)供各個進程使用,稱為 用戶空間。每個進程可以通過系統調用陷入內核態,因此內核空間是由所有進程共享的。雖然說內核和用戶態進程占用了這么大地址空間,但是并不意味它們使用了這么多物理內存,僅表示它可以支配這么大的地址空間。它們是根據需要,將物理內存映射到虛擬地址空間中使用。
????????在Linux中,內核空間是持續存在的,并且在所有進程中都映射到同樣的物理內存。(Linux內核由系統內的所有進程共享)。
?而上面進程虛擬地址空間中的棧區,正指的是我們所說的進程棧。進程棧的初始化大小是由編譯器和鏈接器計算出來的,但是棧的實時大小并不是固定的,Linux 內核會根據入棧情況對棧區進行動態增長(其實也就是添加新的頁表)。但是并不是說棧區可以無限增長,它也有最大限制 RLIMIT_STACK (一般為 8M),我們可以通過 ulimit 來查看或更改 RLIMIT_STACK 的值。
- 線程的內存分布
現代 linux 有多線程(linux 的線程其實是個輕量級的進程),一個進程的多個線程之間共享全局變量、堆、打開的文件…,但棧是不能共享的:棧中各層函數幀代表著一條執行線索,一個線程是一條執行線索,所以每個線程獨占一個棧,而這些棧又都必須在所屬進程的內存空間中。進程的內存分布就變成了下面這個樣子:
2: 內存分區結構
- 代碼區
- 數據區
- 堆
- 棧
- 靜態存儲區
- 全局變量區
- 靜態變量區
- 常量區(靜態常量區)
- 字符串常量
- 常變量區
- 可執行二進制程序 = 代碼段(text)+數據段(data)+BSS段
- 而當程序被加載到內存單元時,則需要另外兩個域:堆域和棧域。
- 一個正在運行的C程序占用的內存區域分為代碼段、初始化數據段、未初始化數據段(BSS)、堆、棧5個部分。
- 正在運行的C程序 = 代碼段(text)+初始化數據段(data)+未初始化數據段(BSS)+堆(heap)+棧(stack)
- 在將應用程序加載到內存空間執行時,操作系統負責代碼段、數據段和BSS段的加載,并將在內存中為這些段分配空間。
- 棧亦由操作系統分配和管理,而不需要程序員顯示地管理;
- 堆段由程序員自己管理,即顯示地申請和釋放空間。
- 動態分配與靜態分配,二者最大的區別在于:
- 直到Run-Time(運行)的時候,執行動態分配
- 而在compile-time(編譯)的時候,就已經決定好了分配多少Text+Data+BSS+Stack。
- 通過malloc()動態分配的內存,需要程序員手工調用free()釋放內存,否則容易導致內存泄露,
- 而靜態分配的內存則在進程執行結束后系統釋放(Text, Data), 但Stack段中的數據很短暫,函數退出立即被銷毀。
進程和線程內存
我們都知道進程運行時,會有一個棧空間(stack)和一個堆空間(heap), 棧空間用于函數調用和局部變量,堆空間是C語言的 malloc 來分配的全局指針。
這些都是進程的私有數據,除了這些,還有映射進來的動態庫,進程間的共享內存等共享空間。另外,操作系統還支持預留虛擬地址空間的功能(延遲分配),也就是在讀寫該內存的時候,操作系統才進行物理內存的分配,因此進程占用的空間情況還是比較復雜的。下面簡單地說明一下。
- VSZ:Virtual Memory Size(虛擬內存大小)。進程占用的全部地址空間,共享庫,預分配內存,交換分區等都包含在里面。因此,它遠遠大于實際的占用的內存空間。
- RSS:Resident Set Size(駐留集大小), 實際占用的物理內存,它包含共享庫,但不包含在交換分區的空間。隨著程序的運行,RSS也會跟著增長,VSZ將是它的上限。
- PSS:Proportional Set Size, 也是實際分配的物理內存,與RSS的區別是,它以平分的方式來計算共享庫的大小(共享庫 / 進程個數), RSS會把共享庫的大小全部計算進來。
- USS:Unique Set Size, 進程的私有內存(獨自使用的庫,堆等空間),不包含共享的內存空間。
- ANON: Anonymous memory,匿名內存 —— 沒有文件關聯的內存頁面。Linux會自動映射文件到內存,讀取的文件后,會自動緩存到內存。如果,應用程序只是使用mmap(MAP_ANONYMOUS) 分配一些內存頁面沒有文件關聯,就稱為“匿名內存”。
- Dirty: dirty pages , 臟頁大小 —— 還沒有寫回到硬盤的緩存頁面。
- VIRT: 同VSZ。
- RES: 同RSS。
內存指標
Item | 全稱 | 含義 | 等價 |
USS | Unique Set Size | 物理內存 | 進程獨占的內存 |
PSS | Proportional Set Size | 物理內存 | Pss =Uss+按比例包含共享庫 |
RSS | Resient Set Size | 物理內存 | RSS=USS+包含共享庫 |
VSS | Virtual Set Size | 虛擬內存 | VSS = RSS+未分配實際物理內存 |
內存的大小關系:VSS>RSS>=PSS>=USS
在實際分析中,一般是以PSS的內存為準,且也是最符合實際情況的統計值
假如進程A(2k),只依賴動態庫B(1000k) ;A 分配 128k的匿名空間,200k的堆棧和堆的空間——實際使用100k。其中動態庫B被 2個進程共享,實際加載200k,那么 ——
VSZ = 2k + 1000k + 128k + 200k = 1230k
RSS = 2k + 200k + 128k + 100k = 430k
PSS = 2k + 200k / 2 + 128k + 100k = 330k
USS = 2k + 128k + 100k = 230k
ANON = 128k
區別
VSS : Virtual Set Size 虛擬耗用內存(包含共享庫占用的內存),即單個進程全部可訪問的地址空間,其大小可能包括還尚未在內存中駐留的部分。對于確定單個進程實際內存使用大小,VSS用處不大。
RSS : Resident Set Size 實際使用物理內存(包含共享庫占用的內存),即單個進程實際占用的內存大小,RSS不太準確的地方在于它包括該進程所使用共享庫全部內存大小。對于一個共享庫,可能被多個進程使用,實際該共享庫只會被裝入內存一次。
PSS : Proportional Set Size 實際使用的物理內存(比例分配共享庫占用的內存)PSS相對于RSS計算共享庫內存大小是按比例的。N個進程共享,該庫對PSS大小的貢獻只有1/N。
USS : Unique Set Size 進程獨自占用的物理內存(不包含共享庫占用的內存)即單個進程私有的內存大小,即該進程獨占的內存部分。USS揭示了運行一個特定進程在的真實內存增量大小。如果進程終止,USS就是實際被返還給系統的內存大小。
PS:下一篇介紹:進程內存分布--之單線程和多線程編寫代碼來內存分布呈現memory-layout.cpp?
關注我,后續還有更多專題博文分享,謝謝!!!??