理解 virt、res、shr 之间的关系(linux 系统篇)

3A网络
• 阅读 75

理解 virt、res、shr 之间的关系(linux 系统篇)

前言

想必在 linux 上写过程序的同学都有分析进程占用多少内存的经历,或者被问到这样的问题 —— 你的程序在运行时占用了多少内存(物理内存)?

通常我们可以通过 top 命令查看进程占用了多少内存。这里我们可以看到 VIRT、RES 和 SHR 三个重要的指标,他们分别代表什么意思呢?

这是本文需要跟大家一起探讨的问题。当然如果更加深入一点,你可能会问进程所占用的那些物理内存都用在了哪些地方?这时候 top 命令可能不能给到你你所想要的答案了,不过我们可以分析 proc 文件系统提供的 smaps 文件,这篇文章详尽地列出了当前进程所占用物理内存的使用情况。

本文将分为三个部分:

1、简要阐述虚拟内存和驻留内存这两个重要的概念;

2、解释 top 命令中 VIRT、RES 以及 SHR 三个参数的实际参考意义;

3、向大家介绍一下 smaps 文件的格式,通过分析 smaps 文件我们可以详细了解进程物理内存的使用情况,比如 mmap 文件占用了多少空间、动态内存开辟消耗了多少空间、函数调用栈消耗了多少空间等等。

一、关于内存的两个概念

要理解 top 命令关于内存使用情况的输出,我们必须首先搞清楚虚拟内存(Virtual Memory)和驻留内存(Resident Memory)两个概念。

(1)虚拟内存

首先需要强调的是虚拟内存不同于物理内存,虽然两者都包含内存字眼但是它们属于两个不同层面的概念。进程占用虚拟内存空间大并非意味着程序的物理内存也一定占用很大。虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。

我们程序中的指针其实都是这个虚拟内存空间中的地址。比如我们在写完一段 C++ 程序之后都需要采用 g++ 进行编译,这时候编译器采用的地址其实就是虚拟内存空间的地址。因为这时候程序还没有运行,何谈物理内存空间地址?凡是程序运行过程中可能需要用到的指令或者数据都必须在虚拟内存空间中。

既然说虚拟内存是一个逻辑意义上(假象的)的内存空间,为了能够让程序在物理机器上运行,那么必须有一套机制可以让这些假象的虚拟内存空间映射到物理内存空间(实实在在的 RAM 内存条上的空间)。这其实就是操作系统中页映射表(page table)所做的事情了。

内核会为系统中每一个进程维护一份相互独立的页映射表。页映射表的基本原理是将程序运行过程中需要访问的一段虚拟内存空间通过页映射表映射到一段物理内存空间上,这样 CPU 访问对应虚拟内存地址的时候就可以通过这种查找页映射表的机制访问物理内存上的某个对应的地址。“页(page)” 是虚拟内存空间向物理内存空间映射的基本单元。

下图演示了虚拟内存空间和物理内存空间的相互关系,它们通过 Page Table 关联起来。其中虚拟内存空间中着色的部分分别被映射到物理内存空间对应相同着色的部分。而虚拟内存空间中灰色的部分表示在物理内存空间中没有与之对应的部分,也就是说灰色部分没有被映射到物理内存空间中。这么做也是本着 “按需映射” 的指导思想,因为虚拟内存空间很大,可能其中很多部分在一次程序运行过程中根本不需要访问,所以也就没有必要将虚拟内存空间中的这些部分映射到物理内存空间上。

理解 virt、res、shr 之间的关系(linux 系统篇)

虚拟内存空间到物理内存空间映射

到这里为止已经基本阐述了什么是虚拟内存了。

总结一下就是,虚拟内存是一个假象的内存空间,在程序运行过程中虚拟内存空间中需要被访问的部分会被映射到物理内存空间中。虚拟内存空间大只能表示程序运行过程中可访问的空间比较大,不代表物理内存空间占用也大。

(2)驻留内存

驻留内存,顾名思义是指那些被映射到进程虚拟内存空间的物理内存。上图中,在系统物理内存空间中被着色的部分都是驻留内存。比如,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(共享)的缩写,它表示的是进程占用的共享内存大小。在上图中我们看到进程 A 虚拟内存空间中的 A4 和进程 B 虚拟内存空间中的 B3 都映射到了物理内存空间的 A4/B3 部分。咋一看很奇怪。

为什么会出现这样的情况呢?

其实我们写的程序会依赖于很多外部的动态库(.so),比如 libc.so、libld.so 等等。这些动态库在内存中仅仅会保存 / 映射一份,如果某个进程运行时需要这个动态库,那么动态加载器会将这块内存映射到对应进程的虚拟内存空间中。多个进展之间通过共享内存的方式相互通信也会出现这样的情况。

这么一来,就会出现不同进程的虚拟内存空间会映射到相同的物理内存空间。这部分物理内存空间其实是被多个进程所共享的,所以我们将他们称为共享内存,用 SHR 来表示。

某个进程占用的内存除了和别的进程共享的内存之外就是自己的独占内存了。所以要计算进程独占内存的大小只要用 RES 的值减去 SHR 值即可。

三、进程的 smaps 文件

通过 top 命令我们已经能看出进程的虚拟空间大小(VIRT)、占用的物理内存(RES)以及和其他进程共享的内存(SHR)。但是仅此而已,如果我想知道如下问题:

进程的虚拟内存空间的分布情况,比如 heap 占用了多少空间、文件映射(mmap)占用了多少空间、stack 占用了多少空间?

进程是否有被交换到 swap 空间的内存,如果有,被交换出去的大小?

mmap 方式打开的数据文件有多少页在内存中是脏页(dirty page)没有被写回到磁盘的?

mmap 方式打开的数据文件当前有多少页面已经在内存中,有多少页面还在磁盘中没有加载到 page cahe 中?

以上这些问题都无法通过 top 命令给出答案,感兴趣的朋友可以在cnaaa服务器上部署环境自己试一下,但是有时候这些问题正是我们在对程序进行性能瓶颈分析和优化时所需要回答的问题。所幸的是,世界上解决问题的方法总比问题本身要多得多。linux 通过 proc 文件系统为每个进程都提供了一个 smaps 文件,通过分析该文件我们就可以一一回答以上提出的问题。

在 smaps 文件中,每一条记录(如下图所示)表示进程虚拟内存空间中一块连续的区域。其中第一行从左到右依次表示地址范围、权限标识、映射文件偏移、设备号、inode、文件路径。详细解释可以参见 understanding-linux-proc-id-maps。

接下来 8 个字段的含义分别如下:

・Size:表示该映射区域在虚拟内存空间中的大小。

・Rss:表示该映射区域当前在物理内存中占用了多少空间      

・Shared_Clean:和其他进程共享的未被改写的 page 的大小

・Shared_Dirty:和其他进程共享的被改写的 page 的大小

・Private_Clean:未被改写的私有页面的大小。

・Private_Dirty:已被改写的私有页面的大小。

・Swap:表示非 mmap 内存(也叫 anonymous memory,比如 malloc 动态分配出来的内存)由于物理内存不足被 swap 到交换空间的大小。

・Pss:该虚拟内存区域平摊计算后使用的物理内存大小 (有些内存会和其他进程共享,例如 mmap 进来的)。比如该区域所映射的物理内存部分同时也被另一个进程映射了,且该部分物理内存的大小为 1000KB,那么该进程分摊其中一半的内存,即 Pss=500KB。

理解 virt、res、shr 之间的关系(linux 系统篇)

smaps 文件中的一条记录

有了 smap 如此详细关于虚拟内存空间到物理内存空间的映射信息,相信大家已经能够通过分析该文件回答上面提出的 4 个问题。

最后希望大家能够通过阅读本文对进程的虚拟内存和物理内存有一个更加清晰认识,并能更加准确理解 top 命令关于内存的输出,最后可以通过 smaps 文件更进一步分析进程使用内存的情况。

点赞
收藏
评论区
推荐文章
红烧土豆泥 红烧土豆泥
1年前
(转载)Java内存区域(运行时数据区域)和内存模型(JMM) - czwbig
转载自:Java内存区域和内存模型是不一样的东西,内存区域是指Jvm运行时将数据分区域存储,强调对内存空间的划分。而内存模型(JavaMemoryModel,简称JMM)是定义了线程和主内存之间的抽象关系,即JMM定义了JVM在计算机内存(RAM)中的工作方式,如果我们要想深入了解Java并发编程,就要先理解好Java内存模型。Java
九路 九路
2年前
Android 内存管理机制
前言:Android系统是基于Linux内核开发的操作系统,而Linux系统有其独到的内存管理机制,会在进程活动停止后结束该进程。Android在此基础上优化了内存管理,会把进程都保存在内存中,直到系统需要更多内存为止,释放部分进程。这些被保存在内存中的进程,并不会影响系统的运行速度,相反,在重新打开这些进程时,会提升进程启动速度Android内存管
Stella981 Stella981
1年前
Linux 内核 VS 内存碎片 (上)
(外部)内存碎片是一个历史悠久的Linux内核编程问题,随着系统的运行,页面被分配给各种任务,随着时间的推移内存会逐步碎片化,最终正常运行时间较长的繁忙系统可能只有很少的物理页面是连续的。由于Linux内核支持虚拟内存管理,物理内存碎片通常不是问题,因为在页表的帮助下,物理上分散的内存在虚拟地址空间仍然是连续的(除非使用大页),但对于需要从内核线性
Stella981 Stella981
1年前
Linux必知必会
!DSC07274(https://oayrssjpa.qnssl.com/DSC07274.jpg)\TOC\本文详细介绍了Linux系统中的free命令的使用方法以及关键参数的含义,这可能是你见过的关于free命令最详细的一篇文章了,绝对值得你收藏。free命令显示了Linux系统中物理内存、交换分区的使用统计信息。
Stella981 Stella981
1年前
Docker运行时的监控
linux的容器依赖cgroups,cgroups不光追踪进程、还跟踪CPU、内存、块IO等使用信息,你可以访问这些度量信息,并且获得网络使用情况的度量信息,这些在纯粹的LXC上可以使用的同样也可以在docker上使用。cgroups通过一个虚假的操作系统暴露出来,在最近的linux发行版中,你可以在操作系统的/sys/fs/cgroup目录下发现这些。在
Stella981 Stella981
1年前
JVM 与 Linux 的内存关系详解
!(https://oscimg.oschina.net/oscnet/2e13817104a0444b97e9720b7a8f54c2.jpg)来源:美团技术团队在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约600m,Linux自身使用大约800m
Easter79 Easter79
1年前
ThreadLocal 内存泄露的实例分析
前言之前写了一篇深入分析ThreadLocal内存泄漏问题(https://my.oschina.net/thinwonton/blog/1505136)是从理论上分析ThreadLocal的内存泄漏问题,这一篇文章我们来分析一下实际的内存泄漏案例。分析问题的过程比结果更重要,理论结合实际才能彻底分析出内存泄漏的原因。案例与分析
Wesley13 Wesley13
1年前
Java oop 第13章_多线程
第13章\_多线程一、多线程相关的概念:1.程序:由某种编程语言开发可执行某些功能的代码组合,它是静态的概念。2.进程:当程序被执行时的过程可以理解为讲程序从外存调入内存的过程,会为每一个程序至少开辟一个独立的内存空间,程序在内存中的状态称为一个进程。3.线程:一个进程至少会有一个独
Wesley13 Wesley13
1年前
Java内存模型详解
内存模型(memorymodel)内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节.不同平台间的处理器架构将直接影响内存模型的结构.在C或C中,可以利用不同操作平台下的内存模型来编写并发程序.但是,这带给开发人员的是,更高的学习成本.相
3A网络 3A网络
2个月前
开发一个不需要重写成 Hive QL 的大数据 SQL 引擎
开发一个不需要重写成HiveQL的大数据SQL引擎学习大数据技术的核心原理,掌握一些高效的思考和思维方式,构建自己的技术知识体系。明白了原理,有时甚至不需要学习,顺着原理就可以推导出各种实现细节。各种知识表象看杂乱无章,若只是学习