


















进程和系统的内存指标很多,不同工具(任务管理器 / 活动监视器等)中的指标定义并不明确,不同操作系统存在名称相似但含义差异很大的术语。
本文抛砖引玉介绍现有 Windows & macOS 操作系统中的内存指标、概念,以便读者后续在使用软件分析、更进一步的内存文章阅读中有更清晰的认知。
进程内存:
可以按照地址空间位置(数据段 / 代码段 /heap/ 内存映射 /stack),访问权限(私有 / 共享),页面位置(物理 / 交换),进行分类
系统内存:主要关注可用内存、总内存指标
Windows :
提交和提交上限
macOS :
虚拟内存:在不同的环境下有不同的语义:
进程:
系统:
尽量避免直接使用虚拟内存的概念,本文未特别说明下,虚拟内存术语为系统的交换空间大小。
Note:文件映射和普通的 fread/fwrite 读写文件接口不同,是文件 IO 的一种方式
写时复制 (copy on write)
具体场景:
代码段
数据段
共享 Sharable/Shared:
私有 :
注意⚠️:匿名内存即使被换出到磁盘上仍然是匿名内存
注意⚠️:读写过程中会使用到物理内存作为页面高速缓存来提高速度
文件读写和文件映射不是等价的概念
页面映射方式作为 x 轴(匿名 / 文件映射)和页面访问权限作为 y 轴(私有 / 共享)四象限:
私有匿名(Anonymous)映射 (#1)
malloc c 标准库接口:动态内存申请方式
平台接口
windows:
共享匿名映射 #2
macos/linux:
windows:
私有文件映射(file-backed) #3
共享文件映射 #4
Windows 上申请内存有两套方式,其中 VirtualAlloc 偏向对虚拟地址维度的操作,而 CreateFileMapping+MapViewOfFile 的组合偏向于文件映射和将文件映射对象映射到进程的虚拟地址空间里,尽管后者一定程度部分包含了前者的功能,但是 VirtualAlloc 支持 reserved / commit 细粒度的申请虚拟地址空间
页面映射位置(物理 / 交换)和页面访问类型(私有 / 共享)四象限 :
工具:vmmap、任务管理器、资源监视器、Process Explorer、Process Hacker
Windows 引入了 " 工作集 " 概念来表示进程虚拟地址空间映射到物理内存(不包含交换到磁盘部分即 page file)的大小
在 macOS 仍然被命名为进程的物理内存,在 Linux 上则使用“resident size” 驻留集术语。
Windows 上进程地址空间有“Reserve 保留”和“Commit 提交”两个重要概念。
在 macOS/Linux 上无完全一致的概念对应,是因为不同操作系统的内存管理有区别,比如 macOS 上交换空间是整个磁盘区域,Linux 上允许通过 vm.overcommit_memory 来过度承诺 commit。这也是为什么会在 Windows 上会出现物理内存充足但是 oom 情况
举个例子,你需要办事预定酒席,可能需要 100 个餐位,但目前确定的人数只有 50 个人,你在自己的小本子上记上可能预定 100 个餐位,打电话给老板的时候只说你暂时只预定 50 个餐位。那这 100 个餐位就是 reserve 的大小,只占据了进程的虚拟地址空间,其中已经确定的 50 个餐位就是 committed 部分。其中 committed 成功后,人并不一定到餐馆了。
下图是不同软件的指标对应关系:
已提交 committed: 进程地址空间的一种状态,表示系统承诺这部分地址空间可以映射到物理内存上。
注意⚠️:在 Windows 任务管理器上的“已提交”不包含共享部分,即值为 private bytes 的值。
私有提交 private bytes: 私有的已提交部分。
⚠️注意:该指标包含未被分配内存的部分,因此不是私有工作集+私有交换到磁盘部分之和。
私有工作集 private workingset: 私有的物理内存。
注意⚠️:在任务管理器默认看到的是这个指标。
Windows 进程指标分为工作集(总 / 共享 / 私有)和提交(私有 / 总)两部分,比较清晰,同时活动监视器指标和 API 接口返回值一致(而 macOS 上的 dirty/clean 的概念就相对复杂)
可通过 windows performance analyzer 分析进程的提交对应堆栈或者系统内存的状态。
macOS 在已有的“共享 / 私有”,“物理 / 交换”进程内存分类上,根据“是否可丢弃”增加一个新的维度:dirty/clean:
clean:这部分内容随时被丢弃后续再通过 page fault 重新读取文件
dirty:这部分内容不能直接丢弃,而只能交换到磁盘或者被压缩。匿名内存
注意⚠️:未特别说明情况下,dirty 不包含 dirty compressed(即内存压缩以及被换出到磁盘的部分)(vmmap 中的定义),而广义的 dirty 和 footprint 概念“接近”。
内存(footprint 指标,Physical footprint): internal + internal_compressed+ iokit 持有的内存 + purgeable_nonvolatile 内存 + 页表,具体构成如下:
活动监视器中默认内存是该指标,这里减掉 alternate_accounting 和减掉 alternate_accounting_compressed,是因为这两个被包含在 internal 和 internal_compressed 里面了,所谓的 internal 就是 dirty 的概念。
footprint 概念和广义的 dirty 概念接近,但 footprint 除了私有匿名内存以外,还额外有:匿名共享内存、purgeable_nonvolatile、iokit_mapped
共享(物理)内存 (Real Shared Memory / RSHRD): 共享的物理内存
VM 被压缩(物理内存) (Compressed Memory): 内存压缩器压缩前的内存大小
footprint:
实际(物理)内存:
专用(物理)内存:
注意文件权限(可读 / 写)、访问权限(私有 / 共享)和共享模式(cow/prv/shared)的区别,即共享内存在没有实际共享的时候,它的共享模式是 PRV 私有
共享(物理)内存:
可清除(物理)内存:
排查内存的软件有 Xcode / Instruments,命令行有 vmmap / footprint / leaks / heap / malloc_history。开启 MallocStackLogging 之后,可以通过命令行工具看到 heap 地址空间对应的分配堆栈。
我们关注进程的内存,主要关注私有部分,即私有物理内存和私有的写入在磁盘空间的内容,这两部分的总和 mac 上是 footprint(内存),而 Windows 上没有这样的概念与之对应。Windows 上更多的是关注私有物理内存的大小。
| macOS | Windows |
|---|---|
| 内存 footprint (注意⚠️:footprint 中会包含共享的匿名内存,和 private bytes 不完全一致,在“一致性指标”中还会提到私有工作集 private bytes) | - |
| - | 私有工作集 private bytes(注意⚠️:该指标是私有的 committed 大小,包含了未被分配物理内存的部分,在“一致性指标”中还会提到) |
| 实际(物理)内存 | 进程工作集 Working Set |
| 专用(物理)内存 | 私有工作集 Private Working Set |
| 共享(物理)内存 | 共享工作集 Sharable Working Set |
进程内存可以按照不同的维度进行分类,比如按照访问权限(私有 / 共享),所在位置(物理 / 交换),是否可丢弃(dirty/clean),是否提交(reserve/commit/free)等。
进程内存的值大小最理想的计算方式是当进程被终止的时候,物理内存 和 page file 能释放的大小总和。
chromium 提出了统一内存指标 的方案,即用来衡量一个进程的内存占用情况是私有内存。
它的定义是:私有的 & 匿名(非文件映射)& 不可丢弃、存在物理内存上或者磁盘上或者被压缩。该指标即私有的 footprint 指标。
各个操作系统中没有直接提供一个 API 来告知一个进程的非共享的内存(物理+page file)是多少,即使拿到所有 regions 的信息后根据页面权限计算(性能会差一些),共享的部分也无法准确知道自己进程使用的那部分,并且进程在磁盘上的占用大小没有接口获取到。
因此 chromium 最终选择的替代接口:
macOS:footprint,这部分相比较理想指标会多计入以下内容:
Windows:private bytes,这部分相比较理想指标会多计入以下内容:
工具:rammap、任务管理器、资源监视器、Process Explorer、Process Hacker
Windows 系统物理内存管理中有两个主要概念:“已提交(committed)” 和 提交上限(commit limit)概念。
下图是不同工具的指标对应关系,对应下表的序号:
| 序号 | 名词 | 解释 |
|---|---|---|
| 1 | 已安装内存 | 内存条的大小 |
| 2 | 总内存 | 已安装 - 为硬件保留的内存 |
| 3 | 已使用(内部可能包含已压缩) | 活跃的被使用的内 ( 注意⚠️:Windows10 上默认开启内存压缩,已压缩也属于已使用的一部分) |
| 4 | 可用 | 总内存 - 使用中 ,即 #11 备用 + #12 真正可用(free) |
| 5 | 已提交 | 映射到物理内存或者交换文件中的地址空间总和。 |
| 6 | 已缓存 | #10 已修改 + #11 备用 |
| 7 | 分页缓冲池 | 内核模式使用的内存区域之一,这部分内存可以换入到磁盘,一般注册表会表示这部分的内存 |
| 8 | 非分页缓冲池 | 内核模式使用的内存区域之一,这部分内存不能换入到磁盘,如进程和线程对象、中断处理程序代码等 |
| 9 | 为硬件保留的内存 | 为 BIOS 和其他外围设备的某些驱动程序保留使用的内存,这部分内存操作系统无法使用和操作的,和驱动程序或者内核 kernel 占用内存不同。 |
| 10 | 已修改 modified | #6 已缓存子集。进程已释放文件缓存中已经被修改的部分,需要回写到磁盘后才能被利用。 |
| 11 | 备用 standby | 已被释放、没被修改,可以直接再利用(包含预读取部分) |
| 12 | 空闲 free | 完全未被使用的内存 |
page fault:如果进程访问的虚拟内存不在进程工作集中,产生缺页中断
软中断 (soft fault,minor page fault) :在物理内存上直接分配并建立映射
硬中断 (hard fault, major page fault) :从磁盘中加载到物理内存后再建立映射
系统物理内存的分类:
可用:
使用中:
已提交:
已使用内存: 使用的物理内存:
已缓存文件: 文件映射内存大小 + 可清理 purgeable 内存大小
已使用的交换: 交换空间占据的磁盘大小
已缓存文件
文件映射内存(私有 / 共享)
| macOS | Windows |
|---|---|
| 总物理内存 | 已安装内存 |
| 总物理内存-已使用内存(注意⚠️:free 可用内存没有包含“已缓存”内容) | 可用物理内存 |
| 已使用内存 | 使用的物理内存 |
| 已缓存( 注意⚠️:和 Windows 的“已缓存”概念不一致 ) | 备用 |
| 已使用的交换(注意⚠️:与 Linux 和 Windows 不同,OSX 不使用预先分配的磁盘分区作为后备存储。相反,它使用机器引导分区上的所有可用空间) | PageFile |
这两者的“已缓存”是不同维度的定义,macOS 上是从 clean 角度定义,主要包含是进程的 clean 类型的内存和一些内存缓存(不属于进程了)。而 Windows 上从工作集是否属于进程来定义的,Windows 上已缓存已经从工作集中移除了。在内容上有一部分交叉。
网络上有这样一句话,“windows 是要多少用多少,而 os x 是有多少用多少。”这句话是说 macOS 上会更多的利用内存做缓存(比如预读或者保留缓存),而 Windows 上则更倾向于更多的可用物理内存。但实际上现代的内存管理机制大同小异,包含内存压缩、预读、缓存机制每个操作系统都是有的。
导致这样的观念深入人心推测可能有几方面因素:
在 Windows 系统中可以清理进程工作集(EmptyWorkingSet)、清空已修改队列(Empty Modified List)来让可用物理内存看上去更多(备用属于可用一部分)。
正如前文提到的,这个机制有一定的意义,比如在硬件差的设备应该尽量保存充足内存避免额外的性能消耗。比如某些进程内存泄漏占用大量物理内存,并不是一无是处。但是在其他一些场景反而会加剧换入换出,比如清理的内存后续马上使用。
因此这个问题不能简单回答,这些工作理论上应该由操作系统更智能的自动完成,操作系统应该考虑各种场景下的内存管理,从而减少用户心智。
macos 上没有等价 reserve 概念:
macos 上没有等价 commit 概念:
正如前文提到,Windows 存在 commit limit 限制,并且存在 commit 后可能不会立即分配内存(virtualAlloc 接口),而是在真正访问的时候才会分配内存。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。