一个由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
关注我,后续还有更多专题博文分享,谢谢!!!