在linux上写过程序的同学一定有分析一个进程占用多少内存的经历,或者被问过这样的问题——你的程序占用了多少内存(物理内存))?通常我们可以使用top命令来查看一个进程占用了多少内存。在这里我们可以看到三个重要的指标:VIRT、RES和SHR。他们的意思是什么?这是本文需要与大家探讨的问题。当然,如果再深入一点,你可能会问进程占用的物理内存用在了哪里?这个时候top命令不一定能给你想要的答案,但是我们可以分析一下proc文件系统提供的smaps文件,里面详细列出了当前进程占用的物理内存的使用情况。本文将分为三个部分。第一部分简要阐述了虚拟内存和常驻内存这两个重要的概念;第二部分解释了top命令中VIRT、RES、SHR这三个参数的实际参考意义;最后一部分给大家介绍了smaps文件的格式,通过分析smaps文件,我们可以了解到进程物理内存的使用情况,比如mmap文件占用了多少空间,消耗了多少空间通过动态内存开发,函数调用堆栈占用了多少空间等等。关于内存的两个概念要理解top命令关于内存使用的输出,首先要理解虚拟内存(VirtualMemory)和常驻内存(ResidentMemory)这两个概念。虚拟内存 首先需要强调的是,虚拟内存不同于物理内存。虽然都包含记忆这个词,但它们属于两个不同层次的概念。一个进程占用大量虚拟内存空间并不意味着程序的物理内存也一定占用大量。虚拟内存是操作系统内核为管理进程地址空间(processaddressspacemanagement)精心设计的内存空间的逻辑概念。我们程序中的指针其实就是这个虚拟内存空间中的地址。比如我们写了一个C++程序之后,需要用g++来编译。这时候编译器使用的地址其实就是虚拟内存空间的地址。因为这个时候程序还没有运行,怎么谈物理内存空间的地址呢?所有在程序运行过程中可能需要用到的指令或数据都必须在虚拟内存空间中。由于虚拟内存是逻辑的(虚构的)内存空间,为了让程序能够在物理机上运行,??必须有一种机制让这些虚拟内存空间映射到物理内存空间(内存条上的真实空间).这其实就是操作系统中的页表所做的事情。内核为系统中的每个进程维护一个单独的页面映射表。.页映射表的基本原理是通过页映射表将程序运行过程中需要访问的一段虚拟内存空间映射到一段物理内存空间,这样当CPU访问对应的虚拟内存空间时内存地址,它可以用它来查找页面映射表。访问物理内存上相应地址的机制。“页”是虚拟内存空间映射到物理内存空间的基本单位。下图1演示了虚拟内存空间和物理内存空间的关系,它们通过页表关联。虚拟内存空间中的彩色部分分别映射到物理内存空间中对应的彩色部分。虚拟内存空间中的灰色部分表示物理内存空间中没有对应的部分,也就是说灰色部分没有映射到物理内存空间中。这也是基于“按需映射”的指导思想,因为虚拟内存空间非常大,在程序运行过程中可能不需要访问其中的很多部分,所以没有必要映射这些部分虚拟内存空间到物理内存空间。至此,什么是虚拟内存已经基本说明了。综上所述,虚拟内存就是一个虚构的内存空间,在程序运行过程中,虚拟内存空间中需要访问的部分会映射到物理内存空间。虚拟内存空间大只能说明程序运行时可访问的空间比较大,并不代表物理内存空间也被占用。图1.虚拟内存空间到物理内存空间的映射复制驻留内存 驻留内存,顾名思义,就是指映射到进程虚拟内存空间的物理内存。在上图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值即可。进程的smaps文件 通过top命令,我们已经可以看到该进程的虚拟空间大小(VIRT)、占用的物理内存(RES)和与其他进程共享的内存(SHR)。但是仅此而已,如果我想知道下面的问题:进程的虚拟内存空间的分布情况,比如heap占用了多少空间,文件映射(mmap)占用了多少空间,还有多少空间被栈占用?该进程是否有交换到交换空间的内存,如果有,换出的大小是多少?mmap打开的数据文件中有多少页是内存中的脏页(dirtypages),还没有写回磁盘?mmap打开的数据文件有多少页当前在内存中,还有多少页还在磁盘上没有加载到页缓存中?等等 以上问题都不是top命令可以回答的,但有时候这些问题恰恰是我们分析和优化程序性能瓶颈时需要回答的问题。幸运的是,世界上解决问题的办法总是多于问题本身。Linux通过proc文件系统为每个进程提供一个smaps文件。通过分析文件,我们可以一一回答上述问题。 在smaps文件中,每条记录(如下图2)代表进程虚拟内存空间中的一块连续区域。第一行从左到右依次表示地址范围、权限标识、映射文件偏移量、设备号、inode、文件路径。详细解释可以在understanding-linux-proc-id-maps中找到。 接下来的8个字段的含义如下:Size:表示映射区域在虚拟内存空间中的大小。Rss:表示映射区当前在物理内存中占用了多少空间私有页面的大小。Private_Dirty:已被覆盖的私有页面的大小。Swap:表示由于物理内存不足而交换到交换空间的非mmap内存(也叫匿名内存,如malloc动态分配的内存)的大小。pss:虚拟内存区摊销计算后所使用的物理内存大小(部分内存会与其他进程共享,如mmap)。比如这块区域映射的部分物理内存也被另外一个进程映射,而这部分物理内存的大小为1000KB,那么进程分配一半的内存,即Pss=500KB。图2.smaps文件中的一条记录Copy 有了smap中虚拟内存空间到物理内存空间映射的这么详细的信息,相信大家分析文件就可以回答上面提出的四个问题了。 最后希望各位读者通过阅读本文能够对进程的虚拟内存和物理内存有更清晰的认识,能够更准确的理解top命令关于内存的输出。最后还可以通过smaps文件进一步分析进程的内存使用情况。
