Windows 的“内存使用量”究竟表示什么——正确解读 Working Set、Private Bytes、Commit 与页面文件

· 更新日期: · · Windows, Windows 开发, 内存管理, Working Set, Private Bytes, Commit, 页面文件, 性能监视, 故障排查, Sysinternals

更新记录(仅首版,2026年08月04日 发布)
首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22175982)

以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。

Go Komura(2026)。《Windows 的“内存使用量”究竟表示什么——正确解读 Working Set、Private Bytes、Commit 与页面文件》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/windows-memory-usage-working-set-commit/

DOI(已登记存档)
10.5281/zenodo.22175982
DOI(上次登记版本)
10.5281/zenodo.22175983

任务管理器里,某个进程的“内存”显示为 1.2GB。可是在 Process Explorer 中,Working Set 是 1.5GB,Private Bytes 是 2.4GB,VMMap 里的 Size 还要更大。再看系统整体,显示的是“已提交 19.6/31.8GB”。

那么,这个应用到底用了多少 GB 内存?

仅凭这些数字对不上,并不能判断其中哪一个是错的。因为想知道什么,决定了该看哪个指标。

当前位于 RAM 中的量、该进程固有的分配量、系统承诺今后也会支撑的量、已预留的虚拟地址范围,这些各不相同。比起“有多少 GB”,先要确认“这是什么的量”。

Windows 的内存指标之所以难懂,是因为它们都用“内存”这同一个词显示出来,实际测量的却是下面这些互不相同的维度。

  • 占用了多少地址空间
  • 消耗了多少提交量(Commit)
  • 当前是否常驻于物理 RAM
  • 这一页是进程固有的,还是可共享的
  • 系统整体还能支撑多少新的分配

本文面向在 Windows 10/11 以及现行 Windows Server 上排查应用内存增长或系统整体内存不足的读者,把 Working Set、Private Working Set、Private Bytes、Commit、Virtual Bytes、页面文件、Available 和页错误的关系串到一张图上。

追查 .NET 对象为什么没有被回收的步骤,见“在 .NET 中区分等待 GC 与内存泄漏”;VMMap 与 Process Explorer 的具体操作,见“Process Explorer / Handle / VMMap 实战”。本文集中讲解作为这些前提的 Windows 操作系统一侧的数字该怎么读

1. 先给出结论

Working Set 是“此刻位于 RAM 中的量”,Private Bytes 是“为这个进程单独承诺的量”,System Commit 是“系统整体承诺的量”。 首先把这三者分开来读。

先对齐各指标回答的问题

想知道的内容 要看的指标 解读时的注意事项
此刻目标进程有多少内容位于 RAM 中 Working Set 不只包含进程固有的页,也包含 DLL 代码、内存映射文件等共享页1
其中只有该进程使用的 RAM 有多少 Private Working Set 它近似于“此刻该进程单独占用的 RAM”,不是应用分配的全部容量2
目标进程固有的提交量有多少 Private Bytes 与是否常驻 RAM 无关,也不是实际写入页面文件的字节数2
系统整体的提交量是否还有余量 “已提交 X/Y” X 是当前量,Y 是上限。X 不是页面文件使用量,Y 大致由 RAM 与页面文件之和决定3

Win32 API 中 PagefileUsage 这个名字也要当心。在现行 Windows 上,它实质上表示与 Private Bytes 相同的 Commit Charge,并不表示实际写入页面文件的量。2

不要只凭“已分配”“变大了”下判断

如果只是把虚拟地址 Reserve 了,那只是为将来使用而把这段范围占住。它并不会消耗同等的 RAM 或提交上限,Reserve 与 Commit 是不同的阶段45

另外,页错误未必伴随磁盘 I/O。 要把能在 RAM 内解决的软页错误,与需要从页面文件、可执行文件、内存映射文件等读取的硬页错误区分开。16

内存泄漏同样不能只凭一次很大的数值判断。要看的是:重复同样的负荷时,Private Bytes 及其构成在处理结束后是否阶梯式上升、是否回不到同一个稳态。要看的不是某一时刻的大小,而是重复之后的底值和斜率。

从症状和目的出发查阅

当前困扰 阅读位置
各工具的数字对不上,不知道该看哪一个 第 2 章:把指标分成四个维度第 9 章:界面与工具的分工
明明有空闲 RAM,却出现 OutOfMemory 3.4:RAM 之外的分配约束第 6 章:系统整体的提交上限
Working Set 或 Private Bytes 持续增长 第 4 章:RAM 常驻量的增减第 5 章:Private Bytes 与释放
拿不定主意要不要缩小或禁用页面文件 6.3:页面文件的作用与大小
RAM 使用率很高,却找不到占用大的进程 第 7 章:物理 RAM 的构成
Page Faults/sec 很高,想知道是不是内存不足 第 8 章:软页错误与硬页错误的区别
想从记录下的数字缩小原因范围,排查泄漏 第 10 章:数字的组合第 11 章:泄漏排查步骤

如果想从机制学起,请从第 2 章按顺序读;如果已经有记录,可以从第 10 章的表开始读。

选择 Windows 的主要内存指标想知道的对象是 RAM 常驻量、进程固有提交量、系统整体提交量还是虚拟地址范围,决定了该看哪个指标此刻位于 RAM 中的量进程固有的承诺量系统整体的承诺量已预留的地址范围想从“内存使用量”里知道什么Working SetPrivate BytesSystem CommitVirtual Bytes / Reserved常驻于物理 RAM进程固有的 Commit与 Commit Limit 比较虚拟地址空间

图1:先把“内存很多”这个观察拆成四类问题。

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 18 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

2. 把“内存使用量”分成四个维度

只要把它理解成对同一批页从不同角度计数,数字的差异就能梳理清楚。这里分成四个维度:地址的状态、数据的后备、是否常驻 RAM,以及能否与其他进程共享。

给一页分类的四个独立维度分别确认虚拟地址的状态、已提交页的后备、是否常驻物理 RAM,以及能否与其他进程共享从四个维度看一页地址状态Free / Reserved / Committed后备Page-file-backed / File-backedRAM 常驻Resident / Not resident可共享性Private / Shareable

图2:即便是同一页,地址状态、后备、常驻和共享性也是分别决定的。

不要把状态、后备和可共享性混为一谈

Mapped 并不是与 Free、Reserved、Committed 并列的地址状态,而是区域的种类。映射视图的页同样可以是 Committed。

另一方面,Private 不是后备介质,而是可共享性的分类。后备要看是 Page-file-backed 还是 File-backed,可共享性要看是 Private 还是 Shareable,两者分开读。

比较各指标分别统计了哪些页

把这四个维度组合起来,几个代表性指标的关系如下。

页的状态 Working Set Private Working Set Private Bytes Virtual Bytes 系列
进程固有、已提交、常驻 RAM 计入 计入 计入 计入
进程固有、已提交、未常驻 RAM 不计入 不计入 计入 计入
DLL 或映射文件的共享页、常驻 RAM 计入 原则上不计入 原则上不计入 计入
已预留但未提交 不计入 不计入 不计入 可能计入
未使用的地址范围 不计入 不计入 不计入 通常不计入
页的种类与主要内存指标的对应关系展示常驻的 Private 页、未常驻的 Private 页、常驻的共享页以及仅预留的范围分别被计入哪些指标Private、已 Commit、常驻 RAMPrivate、已 Commit、未常驻 RAM共享页、常驻 RAMReserved、未 CommitWorking SetPrivate Working SetPrivate BytesVirtual Bytes 系列

图3:Working Set 与 Private Bytes 统计的是不同的页集合,因此不构成简单的包含关系。

Working Set 与 Private Bytes 之间没有固定的大小关系

这里的关键在于,Working Set 与 Private Bytes 并不是简单的包含关系

Private Bytes 中包含进程固有、但当前并未常驻 RAM 的页。而 Working Set 中包含 DLL 代码、共享内存等 Private Bytes 不统计的共享页。因此,随进程和时点不同,Working Set 可能大于 Private Bytes,也可能相反。

另外,把多个进程的 Working Set 简单相加,可能把共享 DLL 等同一张物理页重复计算多次。“各进程 Working Set 之和=正在使用的 RAM”并不成立。

3. 虚拟地址空间——Reserve 与 Commit 是两回事

“预留地址”“提交”“首次触碰并载入 RAM”是不同的事件。本章从这些差别讲起,一直讲到为什么有空闲 RAM 也会分配失败。

3.1. 虚拟地址不是物理 RAM 的地址

每个进程都有自己专用的虚拟地址空间。应用操作的指针并不直接指向物理 RAM 的位置,而是由 Windows 通过页表把虚拟地址映射到物理页或文件上的数据。7

因此,即使 PC 装了 64GB RAM,某个 32 位进程可用的虚拟地址空间通常也远小于这个数字。反过来,虚拟地址空间大于物理 RAM 的 64 位进程也很常见。

3.2. Reserved 只是“占住了门牌号”

VirtualAllocMEM_RESERVE 为将来使用而预留一段连续的虚拟地址范围。在这个阶段,页上并没有关联物理存储,也不能对该范围读写。45

例如,数据库或运行时为将来的增长预留了 8GB 地址范围,这本身并不意味着消耗了 8GB RAM 或 8GB 的 Private Bytes。

3.3. Committed 是“需要时会支撑”的承诺状态

MEM_COMMIT 把该虚拟页置为 Committed 状态,是 Windows 承诺提供必要后备的操作。提交的那一刻,就会计入系统的 Commit Charge。5

已 Commit 未必就能读写

读取、写入、执行的许可由 PAGE_READONLYPAGE_READWRITEPAGE_EXECUTEPAGE_NOACCESS 等页面保护属性单独决定。已 Commit 本身并不等于“可读写”。5

Commit 与常驻 RAM 也是不同的阶段

实际的物理页有可能直到首次访问时才分配。首次触碰的页会被清零初始化,经过按需置零错误进入 Working Set。51

因此,同样是“已分配”,也分为下面三个阶段。

从 Reserve 到 Commit 再到常驻 RAM 的三个阶段展示预留虚拟地址、提交页面、首次访问时分配物理页并进入 Working Set 的流程MEM_COMMIT首次访问触发按需置零错误若一直未访问MEM_RESERVE 预留地址范围计入 Virtual Bytes 系列已 Commit,按页面保护属性决定可否访问计入 Private Bytes / System Commit分配物理页并常驻 RAM计入 Working Set已 Commit 但未常驻

图4:Reserve、Commit 与首次访问是不同的事件,各自推动不同的指标。

这三个阶段分别推动 Virtual Bytes 系列、Private Bytes 和 Working Set 的数字。

3.4. 有空闲 RAM 也会出现 OutOfMemory 的原因

内存分配成功与否,并不只由空闲 RAM 决定。

  • 进程的虚拟地址空间已经用尽
  • 没有足够大的连续空闲地址范围
  • 系统整体的 Commit Charge 达到了 Commit Limit
  • Job Object、容器、运行时、库有各自的上限
  • 该进程是 32 位进程
  • 本机堆已经碎片化

即便在 64 位 Windows 上,32 位进程的用户模式虚拟地址空间在未启用 IMAGE_FILE_LARGE_ADDRESS_AWARE 时通常也是 2GB。启用了该标志的 32 位应用在 64 位 Windows 上最多可以使用 4GB。8

所以,“PC 还有 20GB 空闲 RAM,32 位应用却在 1.6GB 附近失败”并不矛盾。问题不在 RAM,而可能是撞上了地址空间的碎片化或上限。

4. Working Set——此刻位于 RAM 中的页

Working Set 是进程虚拟地址空间中当前常驻于物理 RAM 的那批页。1

其中混合着下面这些内容。

  • 进程固有的堆和栈
  • EXE 与 DLL 的代码、只读数据
  • 内存映射文件
  • 共享内存
  • 写时复制之后变成该进程固有的页
  • 运行时和各类库触碰过的页

4.1. Working Set 增加,未必说明分配增加了

增加时:也可能只是原有的页进入了 RAM

首次访问已经提交过的页时,可能出现 Private Bytes 不变、只有 Working Set 增加的情况。

把大文件做内存映射并顺序读取时,也可能只是来自文件的页进入了 Working Set,而 Private Bytes 几乎不增加。

减少时:也可能仍然持有同样的内存

当 Windows 因内存压力对 Working Set 执行裁剪时,应用逻辑上仍持有同样的内存,只有 Working Set 减少了。之后再次触碰这些页,它们会经由页错误回来。

也就是说,Working Set 上升不一定是“新分配了”,下降也不一定是“释放了”。 要把常驻状态的变化与分配、释放分开来读。

只有 Working Set 增减的典型流程同一批已提交页在首次访问时进入 RAM,被裁剪后变为未常驻,再次访问时回来,而这期间 Private Bytes 一直计入首次访问内存压力下被裁剪再次访问触发页错误同一批已 Commit 的页常驻 RAM未常驻计入 Working Set不计入 Working SetCommit 期间一直计入 Private Bytes

图5:Working Set 随常驻状态上下波动,但只要同一批页的 Commit 还在,Private Bytes 就不会减少。

4.2. Working Set 中包含共享页

如果同一个 DLL 的代码页被 10 个进程共享,这些页可能出现在各个进程的 Working Set 里,而物理 RAM 上可能只存在一份。Working Set 之和超过装机 RAM,并不立刻说明异常。

想更接近“此刻只有该进程单独占用的 RAM”,就要看 Private Working Set。不过,它同样不是“该进程分配的全部内存”,而只是当前常驻的 Private 页

4.3. 强行缩小 Working Set 修不好泄漏

EmptyWorkingSetSetProcessWorkingSetSize 可以把页从进程的 Working Set 中赶出去。但这不是释放提交量或堆上引用的操作。可能出现 Private Bytes 不变、只有表面上的 RAM 使用量下降,而下一次访问时页错误增多的情况。9

如果只有按下“削减内存”按钮的那一刻任务管理器的数字变小,一恢复操作马上又回到原样,那很可能不是“释放”,而只是裁剪了 Working Set。

5. Private Bytes——进程固有的提交量

Private Bytes 是为该进程专用提交的虚拟内存量。它表示无法与其他进程共享的 Commit Charge,与当前是否常驻 RAM 无关。在 Microsoft 的 PROCESS_MEMORY_COUNTERS_EX 中,PrivateUsage 对应这个值。102

Win32 API 里还有 PagefileUsage 这个容易混淆的字段名,但现行文档把它定义为“该进程的 Commit Charge”,并说明它与 PrivateUsage 是同一个值。也就是说,Private Bytes 为 2GB 并不意味着“向 pagefile.sys 写了 2GB”。2

影响 Private Bytes 的典型因素有下面这些。

  • HeapAllocmallocnew 等使用的本机堆的提交
  • VirtualAlloc 直接提交的 Private Data
  • .NET GC 堆中已提交的区域
  • 线程栈中实际已提交的部分
  • 写时复制视图(FILE_MAP_COPY)在映射时为整个视图预留的 Commit Charge
  • 库或设备 SDK 内部持有的 Private 缓冲区

写时复制还要注意:它在实际写入之前就已经计入了。FILE_MAP_COPY 创建的视图,其中每一页将来都可能私有化,因此在映射的那一刻就会预留 Commit Charge,以保证整个视图都能由页面文件后备。11

所以,即使还没有实际写入、还没有生成 Private 副本,System Commit 和进程的 Commit Charge(Private Bytes)也可能按整个视图的大小增加。11

5.1. 为什么 free 或 GC 之后 Private Bytes 也不下降

把应用内的释放与归还给操作系统的操作分开

即使从应用看来内存已经“释放”,运行时或堆分配器也可能不把这块区域 Decommit 给操作系统,而是留着将来复用。这时,应用内部虽然可以复用,Private Bytes 却仍然很高。

此外,大块区域只有一小部分还活着、碎片化、缓存或池已经预热到上限等原因,也会让数值居高不下。

不要看居高不下的数值,而要比较同样负荷之后的变化

Private Bytes 高本身证明不了泄漏。按下面的顺序比较时间差。

  1. 把同样的处理重复同样的次数。
  2. 处理结束后留出同样长的等待时间。
  3. 确认 Private Bytes 是回到同一水平,还是在某个值上封顶。
  4. 用 VMMap 或堆转储确认是哪个区域、哪种类型增加了。
free 或 GC 之后 Private Bytes 也不下降的原因分配器把应用不再需要的区域归还操作系统,与留着复用,两种情况下 Private Bytes 的变化不同Decommit / Release留着复用应用通过 free / GC 标记为不再需要分配器是否归还给操作系统Commit Charge 减少Private Bytes 下降区域保持已 Commit 状态Private Bytes 居高不下池、缓存、碎片化

图6:在应用内部变得可以复用,与把 Commit 归还给操作系统,不是一回事。

5.2. 高度可疑的泄漏形态

像下面这样,每跑一轮负荷底值就抬高一级的“阶梯状”增长,需要重点关注。

Private Bytes
  ^
  |                    ________
  |             ______|
  |      ______|
  |_____|
  +----------------------------> 重复同一处理

不过,即使是阶梯状,也可能只是首次 JIT、字体、图像解码器、连接池、缓存预热带来的几次增长,之后就稳定下来。比起是否在增加,更重要的是是否收敛到稳态

6. System Commit——“已提交 X/Y”到底是什么

前面看的主要是单个进程的指标。任务管理器“性能”→“内存”里的“已提交 X/Y”是系统整体的指标,要与单个进程的值分开读。

  • X:System Commit Charge
    当前 Windows 在系统整体范围内承诺支撑的已提交内存
  • Y:System Commit Limit
    系统能够支撑的提交量上限

Commit Limit 大致由物理 RAM 与所有页面文件之和决定。没有页面文件时,会比装机 RAM 略小一些。36

System Commit Charge 与 Commit Limit 的关系进程固有、共享节和内核的提交构成当前值 X,物理 RAM 与页面文件支撑上限 YX 不能超过 Y各进程的 Private CommitSystem Commit Charge(X)由页面文件后备的共享节的 Commit内核的 Commit物理 RAMSystem Commit Limit(Y)页面文件

图7:X 是当前的承诺量,Y 是能够支撑这些承诺的上限,它不是页面文件使用量的显示。

System Commit Charge 中不仅有各进程 Private Bytes 之和,还包含由页面文件后备的共享节的 Commit,以及内核消耗的 Commit。因此,仅靠各进程 Private Bytes 之和无法完整解释 X。

6.1. Commit Charge 不是页面文件使用量

“20/31GB”里的 20GB 不是磁盘上的量

设想一台 RAM 16GB、页面文件 16GB、Committed 为 20/31GB 的系统。

这里的 20GB 并不意味着“已经向页面文件写入了 20GB”。20GB 是指:对于 Private 的可修改页等,Windows 承诺在需要时提供 RAM 或页面文件等后备的总量。

这 20GB 中混合着下面这些状态。

  • 大部分常驻在 RAM 中
  • 一部分已换出到页面文件
  • 已 Commit 但还从未被首次访问
  • 作为内核一侧的提交被消耗

页面文件的使用率要用另外的计数器看

想看页面文件的实际使用率,就要在 Commit 之外确认 Paging File(*)\% Usage。不过 Microsoft 的资料也说明,仅凭页面文件使用率高并不一定是性能问题,需要结合是否到达 Commit Limit、Modified Page List 以及实际的分页 I/O 来判断。6

6.2. 接近 Commit Limit 时会发生什么

当 System Commit Charge 达到 Commit Limit 时,就无法再支撑新的提交请求,会导致进程内存分配失败、应用崩溃、无法操作等后果。3

这里比“空闲 RAM”更重要的是 Commit 的 X/Y。即使裁剪 Working Set 腾出空闲 RAM,只要 Commit Charge 本身没有减少,到达 Commit Limit 的状况也不会缓解。

6.3. 页面文件的三个作用

页面文件主要有下面几个作用。

  1. 扩大 Commit Limit
  2. 让使用频率低的已修改页可以从 RAM 换出
  3. 按配置支撑系统崩溃转储

禁用或改大小,要按峰值提交量和转储需求判断

禁用页面文件与“磁盘 I/O 必然减少、系统变快”之间并不存在这么简单的关系。相反,它会降低 Commit Limit,让已修改但暂时不用的页更容易留在 RAM 中,还可能导致崩溃时无法采集所需的转储。36

页面文件的合适大小并不只由装机 RAM 决定。Microsoft 也说明,由于峰值 System Commit Charge 和所需崩溃转储的种类因系统而异,无法一概而论。6

7. 物理 RAM 的构成——只看 Available 少不能下判断

物理 RAM 并不只用于用户进程的 Working Set。

  • 各进程的 Working Set
  • 系统文件缓存
  • Standby、Modified、Free、Zeroed 等页列表
  • 内核的 Paged Pool / Nonpaged Pool
  • 设备驱动程序持有的内存
  • 内存压缩的存储
  • 与 GPU 及设备共享或预留的区域
  • 硬件保留

7.1. Available 中也包含可以复用的缓存

Free 与 Available 不是一回事。 Windows 的 Available MBytes 中除了 Free、Zeroed,还包含必要时可以复用的 Standby 页。它不是只统计完全未使用的 RAM 的指标。12

  • Free:当前没有分配给任何用途的页
  • Zeroed:为了能安全交给其他进程而已清零的页
  • Standby:已经离开 Working Set,但内容仍缓存在 RAM 中的页
  • Modified:内容已被修改,复用前需要写回到合适的后备存储的页
Working Set 与各页列表之间的迁移展示使用中的页如果未修改就转到 Standby、已修改就转到 Modified,并经过再次访问、写回和复用的流程移出未修改的页移出已修改的页写回完成再次访问改作他用清零分配后访问Working Set(使用中)Standby(保留内容的复用候选)Modified(等待写回)分配给其他用途Free(未使用)Zeroed(可用于新分配)计入 Available

图8:Available 中不仅有完全空闲的部分,还包含必要时可以复用的 Standby。

把 Free 少与物理内存紧张分开看

“为了增加空闲 RAM 而把缓存全部丢掉”并不总是划算。只要 Standby 上还留着需要的数据,再次访问时就不必读磁盘,可以很快回到 Working Set。

所以,即使任务管理器里 Free 很少,只要 Available 足够,硬页错误和磁盘等待也没有成为问题,那可能只是 Windows 把 RAM 有效地当作缓存在用。

7.2. 没有占用大的进程,RAM 却在减少

把各进程的 Private Working Set 加起来也解释不了的内存消耗并不罕见。

  • 文件缓存和内存映射文件
  • Nonpaged Pool / Paged Pool
  • 驱动程序锁定的页
  • 共享页
  • 内存压缩
  • 虚拟化和 GPU 相关的分配

这种情况下,与其一直盯着进程列表,不如用 Sysinternals 的 RAMMap 确认 Use Counts、Processes、Priority Summary 和 File Summary。RAMMap 是把物理内存按用途、页列表和文件分解的官方工具。13

如果只有 Nonpaged Pool 在持续增长,就到了应该怀疑驱动程序或内核一侧泄漏、而不是用户模式应用 Private Bytes 的阶段。

8. 页错误——数量多本身并不是异常

当进程访问不在当前 Working Set 中的页时,就会发生页错误。名字里虽然有“Fault”,但它不是异常故障,而是驱动虚拟内存运转的正常机制。1

首先要区分的是,是否需要读磁盘。 在此基础上,不仅看发生次数,还要结合实际的等待时间和响应劣化一起看。

8.1. 软页错误

指不读磁盘就能解决的那些。

  • 页还留在 Standby 或 Transition 中
  • 其他进程的 Working Set 中有同一张共享页
  • 首次访问已 Commit 的页,分配一张零页
  • 内存管理器的预读已经把它放进了 RAM

因此,即使 \Memory\Page Faults/sec 很大,也不一定发生了磁盘 I/O 或延迟。

8.2. 硬页错误

指需要从磁盘上的后备存储读取内容的那些。读取来源不限于页面文件。

  • .exe.dll 的代码与数据
  • 内存映射文件
  • 页面文件
软页错误与硬页错误的分支访问不在 Working Set 中的页时,不需要存储 I/O 的按软页错误处理,需要的按硬页错误处理否(Standby、共享、按需置零等)访问不在 Working Set 中的页是否需要存储 I/O软页错误不读磁盘就进入 Working Set硬页错误从哪里读取EXE / DLL内存映射文件页面文件读取后进入 Working Set

图9:仅凭页错误这个名字,判断不出是否发生了磁盘 I/O。

Microsoft 把 \Memory\Pages/sec\Memory\Page Reads/sec\Memory\Pages Input/sec 等列为测量硬页错误的计数器。即便这些值很高也不一定是低内存,因此要与 Available MBytes、磁盘延迟和实际响应时间做相关分析。6

8.3. 不要设统一的阈值

“Page Faults/sec 超过 1000 就是异常”这类固定值,会随存储、页大小、工作负载和访问局部性而含义不同。

实际工作中,把下面这些放到同一条时间轴上。

  • Memory\Available MBytes
  • Memory\Pages Input/sec
  • Memory\Page Reads/sec
  • 目标磁盘的 Read latency / Queue
  • 目标进程的 Working Set 与 Private Bytes
  • 应用的处理时间、超时、UI 响应

如果负荷上升的同时 Available 下降、Pages Input/sec 和磁盘等待上升、处理时间也变差,那就凑齐了怀疑物理内存紧张导致分页的依据。

9. 用哪个界面、哪个工具看什么

先确定是单个进程还是系统整体、是某一时刻的构成还是时间序列,选工具就容易了。先用下表把指标和工具对应起来,再进入记录环节。

想知道的内容 首先要看的指标 主要工具
此刻目标进程放进 RAM 的量 Working Set 任务管理器、Process Explorer、Get-Process
其中进程固有的 RAM Private Working Set / Working Set - Private 任务管理器的详细信息列、Process Explorer、PerfMon
目标进程固有的提交量 Private Bytes / Commit Size Process Explorer、PerfMon、VMMap、Get-Process
进程的虚拟地址范围 Virtual Bytes / Size Process Explorer、VMMap、Get-Process
系统整体的提交余力 Committed Bytes / Commit Limit 任务管理器“性能”、PerfMon
物理 RAM 的复用余力 Available MBytes 任务管理器、PerfMon
Standby、Modified、文件缓存的构成 页列表与按用途的构成 RAMMap
Private Bytes 中是什么增加了 Heap / Private Data / Managed Heap 等 VMMap、WinDbg、各运行时的转储
伴随磁盘的分页 Pages Input/sec、Page Reads/sec、磁盘延迟 PerfMon、WPR/WPA
选择 Windows 的内存调查工具根据对象是单个进程还是系统整体、是某一时刻还是时间序列、是否要追到运行时内部来选择工具某一时刻的构成时间序列系统整体物理 RAM 的构成含 CPU、I/O 与等待的时间轴.NET 堆本机堆想排查什么对象是单个进程吗某一时刻还是时间序列VMMapPerfMon / PowerShell物理 RAM 的构成还是时间轴RAMMapWPR / WPA是否要追到运行时内部的持有dotnet-dump / PerfViewWinDbg / Application Verifier

图10:先确定对象范围和时间轴,才能不多不少地选出需要的工具。

9.1. 任务管理器

在任务管理器里,要分界面来看。

  • “进程”或“详细信息”:单个进程的 Working Set 系列、Commit Size 系列
  • “性能”→“内存”:系统整体的“正在使用”“可用”“已提交”“已缓存”“分页缓冲池”“非分页缓冲池”

不要只看“内存”这个列名就下判断,而要在“详细信息”选项卡右键点击列标题,把 Working Set、Peak Working Set、Commit Size 等需要的列加上。列名会因 Windows 版本和显示语言略有差异,因此要确认列的含义之后再记录

9.2. 用 PowerShell 采集时间序列

先记录同一个进程的三项指标

如果已经知道目标进程的 ID,可以用 Get-Process 同时采集 Working Set、Private Bytes 和 Virtual Bytes 的斜率。

param(
    [Parameter(Mandatory)]
    [int]$ProcessId,

    [int]$IntervalSeconds = 5,
    [int]$SampleCount = 60
)

$samples = for ($i = 0; $i -lt $SampleCount; $i++) {
    $process = Get-Process -Id $ProcessId -ErrorAction Stop

    [pscustomobject]@{
        Timestamp      = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        ProcessId      = $process.Id
        WorkingSetMB   = [math]::Round($process.WorkingSet64 / 1MB, 1)
        PrivateBytesMB = [math]::Round($process.PrivateMemorySize64 / 1MB, 1)
        VirtualBytesMB = [math]::Round($process.VirtualMemorySize64 / 1MB, 1)
        Handles        = $process.HandleCount
        Threads        = $process.Threads.Count
    }

    Start-Sleep -Seconds $IntervalSeconds
}

$samples | Format-Table -AutoSize
$samples | Export-Csv .\memory-samples.csv -NoTypeInformation -Encoding utf8

.NETProcess.WorkingSet64 对应 Working Set,PrivateMemorySize64 对应 Private Bytes,VirtualMemorySize64 对应 Virtual Bytes。141516

防止多实例或重启导致认错对象

对于存在多个实例的应用,请按 PID 而不是名称跟踪。在 PID 会因重启而改变的长期监视中,需要记录启动时刻、服务名等信息,设计上要保证不会认错对象。

9.3. 用 PerfMon 把系统和进程放到同一条时间轴

至少同时记录下面这些,排查起来会更容易。

\Process(<对象>)\ID Process
\Process(<对象>)\Working Set
\Process(<对象>)\Working Set - Private
\Process(<对象>)\Private Bytes
\Process(<对象>)\Virtual Bytes

\Memory\Available MBytes
\Memory\Committed Bytes
\Memory\Commit Limit
\Memory\Pages Input/sec
\Memory\Page Reads/sec
\Memory\Pool Nonpaged Bytes
\Memory\Pool Paged Bytes

不要只看实例名,还要确认每个采样的 PID

当存在多个同名进程,或监视过程中发生重启时,仅靠 Process(name)Process(name#N) 这样的实例名无法锁定对象。每个采样都要记录 ID Process,只采用该值与跟踪目标 PID 一致的实例。如果跨越了 PID 发生变化的重启,还要另外记录切换的时刻。

找不到计数器时,先确认显示语言

Windows 的性能计数器名称可能随显示语言本地化。如果在 PowerShell 中直接指定英文名找不到,可以从 PerfMon 的图形界面添加,或用 Get-Counter -ListSet * 确认本地环境中的名称。

9.4. 不要混淆 VMMap 与 RAMMap 的职责

  • VMMap:把单个进程的虚拟内存和 Working Set 分解为 Heap、Image、Mapped File、Private Data、Managed Heap 等
  • RAMMap:把系统整体的物理 RAM 分解为用途、页列表、进程和文件

“这个进程的 Private Bytes 是什么增加的”用 VMMap,“进程列表解释不了的 RAM 用到哪里去了”用 RAMMap。1713

10. 从数字的组合读出症状

记录下来的值,要看哪些指标一起变化、哪些指标没有变化。下表不是用来断定原因的,而是用来选择下一步要确认哪些构成和条件的。

观察到的形态 首先要考虑的 接下来要确认的
Working Set 增加,Private Bytes 稳定 对已有页的首次访问、共享 DLL、映射文件、文件缓存 VMMap 的 Image / Mapped File、Pages Input/sec
Private Bytes 增加,Working Set 稳定 Private 的提交增加了,但未常驻或被裁剪 VMMap 的 Heap / Private Data / Managed Heap
两者都在启动后立刻增加,之后走平 JIT、缓存、池、初始化的预热 再追加同样的负荷是否还会增加
每跑一轮负荷 Private Bytes 的底值就抬高 泄漏、无上限的缓存、释放后仍持有的分配器 前后的 VMMap 快照、堆转储
只有 Working Set 骤降,一操作就回来 操作系统或应用裁剪了 Working Set Private Bytes、Pages Input/sec、响应时间
Committed X/Y 中的 X 接近 Y 系统整体的提交紧张 Private Bytes 排名靠前者、Paged/Nonpaged Pool、页面文件配置
Available 很低,Pages Input/sec 与磁盘延迟很高 物理 RAM 紧张与硬分页 Working Set 排名靠前者、RAMMap、与工作负载的相关性
RAM 使用率很高,却没有占用大的进程 缓存、共享页、内核池、驱动程序、压缩等 RAMMap、Pool Nonpaged/Paged Bytes
有空闲 RAM,却只有 32 位应用失败 虚拟地址空间上限与碎片化 VMMap 的 Free/Reserved、可执行文件的 LAA 配置
Private Bytes 很高,但重复处理也不再增加 可能是保持高水位的池或缓存 上限、复用情况、峰值之后的稳定性

这张表里最重要的是,不要看单个值,而要按组合来读

11. 内存泄漏排查的实操步骤

排查按确定比较条件 → 同时记录 → 找出增加的指标 → 分析其构成 → 比较修改前后这五个阶段推进。不要一发现大数值就直接去抓转储,而要先缩小“什么在增加”的范围。

11.1. 先确定复现条件和稳态点

只说“几天下来会涨”是没法比较的。为了能在同样条件下比较正常版和问题版,先确定下面这些。

  • 启动后的预热要包含到哪一步
  • 一个周期的操作内容
  • 一个周期结束后等待多少秒
  • 到达缓存上限需要跑多少次
  • 正常版与问题版能否使用同样的输入

11.2. 同时记录进程与系统

至少要按同一时刻留下下面这些。

  • 目标的 Working Set
  • 目标的 Private Bytes
  • 目标的 Virtual Bytes
  • 系统的 Committed Bytes / Commit Limit
  • Available MBytes
  • Pages Input/sec
  • 句柄数、线程数
  • 操作次数与处理件数

如果进程的 Private Bytes 稳定,只有系统的 Commit 在增加,就需要把视野扩大到其他进程、内核、驱动程序、共享节等。

11.3. 先确定在增加的“维度”

  • 只有 Working Set:常驻页、来自共享与文件的页、裁剪与重新读取
  • Private Bytes:进程固有的提交量
  • 只有 Virtual Bytes:Reserve、映射、地址空间碎片化
  • 只有 System Commit:也包含其他进程和内核一侧
  • Nonpaged Pool:驱动程序与内核一侧
  • Handles / GDI / USER:内存以外的资源泄漏

跳过这个顺序直接抓转储,就会在对象都搞错的情况下去读海量信息。

11.4. 进入构成分析

  • 本机进程:VMMap、WinDbg、Application Verifier、堆跟踪
  • .NET:dotnet-countersdotnet-gcdumpdotnet-dump、PerfView
  • 系统整体:RAMMap、PerfMon、WPR/WPA
  • 内核池:PoolMon、WinDbg

VMMap 会按种类显示进程已提交的虚拟内存,以及分别分配给它们的 Working Set。能把 Private Bytes 的增长缩小到“Heap”“Private Data”“Managed Heap”“Mapped File”中的哪一类,会极大影响后续的排查成本。17

11.5. 修改后要在同样条件下比较斜率

只比较修改前后的峰值是不够的。初值不同的话,很容易出现反转。要对齐下面这些条件,比较每个周期结束后的底值和斜率。

  • 同样的启动状态
  • 同样的输入
  • 同样的操作次数
  • 同样的等待时间
  • 同样的采样间隔

证明泄漏已修复,靠的不是“最大值变小了”,而是重复同样的负荷时增长会收敛

12. 换个说法讲清常见误解

误解 1:任务管理器的内存=应用分配的全部容量

换个说法: 先确认是哪一列。Working Set 系列是当前常驻 RAM 的量,Commit Size 系列是进程固有的提交量。

误解 2:Private Bytes=页面文件上的字节数

换个说法: Private Bytes 是 Private 的 Commit Charge。它是一个逻辑上的承诺量,既包含位于 RAM 中的页,也包含必要时由页面文件支撑的页。

误解 3:Commit X/Y=页面文件使用量/页面文件容量

换个说法: X 是系统整体的 Commit Charge,Y 是 Commit Limit。页面文件会扩大 Y,但 X 并不会直接变成磁盘上的使用量。

误解 4:Page Faults/sec 高=正在向磁盘交换

换个说法: 其中也包含软页错误。是否伴随磁盘 I/O,要用 Pages Input/sec、Page Reads/sec 和磁盘延迟来确认。

误解 5:Free RAM 少=内存不足

换个说法: 要看 Available、Standby、硬分页和响应时间。用可复用的缓存填满 RAM 是正常现象。

误解 6:能把 Working Set 压小=修好了内存泄漏

换个说法: 可能只是把页从 RAM 赶出去了。要确认 Private Bytes 或堆内的持有是否减少。

误解 7:Private Bytes 增加=确定是泄漏

换个说法: 要先确认重复同样的工作负载时是否收敛、增加的是哪一类内存、是不是可以释放的缓存,才能下判断。

13. 小结

首先确定数字统计的是什么

Windows 的“内存使用量”不是一个数字。要把地址空间、提交量、RAM 常驻和可共享性分开考虑。

Working Set 是当前位于 RAM 中的页,其中既有 Private 也有 Shared。Private Working Set 是其中进程固有的常驻页。而 Private Bytes 是进程固有的 Commit Charge,它既不是当前位于 RAM 中的量,也不是实际写入页面文件的量。

其次把分配、常驻与分页分开读

Reserve 的虚拟地址、Commit 的页、实际触碰后进入 Working Set 的页,是不同的阶段。

Committed X/Y 表示系统整体的 Commit Charge / Commit Limit。页面文件主要支撑三件事:扩大 Commit Limit、换出已修改页、支撑崩溃转储。

页错误是正常行为,软页错误不读磁盘。硬页错误的读取来源也不只有页面文件,还包括 EXE、DLL 和映射文件。

排查时要比较同样负荷后的底值、斜率与构成

内存泄漏不是靠某一时刻的大小,而是靠同样负荷后的底值、斜率和构成来证明。

单个进程的构成用 VMMap,系统整体的物理 RAM 用 RAMMap,时间序列用 PerfMon,运行时内部则进一步用专门的转储工具,这是基本路线。

下次在任务管理器里发现“内存在涨”时,请先这样反问一句。

在涨的是 Working Set、Private Bytes、Virtual Bytes,还是 System Commit。

仅凭这一问,排查的入口就会准确得多。

相关文章

相关咨询领域

小村软件有限公司承接 Windows 应用程序的内存增长、长期运行后的性能劣化、32 位进程的 OutOfMemory,以及只在客户环境出现的内存不足等问题的原因调查,会结合 PerfMon、VMMap、RAMMap、WinDbg 与 .NET 诊断工具来开展。我们不会止步于“内存很多”,而是把哪个区域、在哪个操作中、为什么增长、被谁引用和持有都排查清楚。

参考链接

  1. Microsoft Learn, Working Set. 关于进程的 Working Set 是当前常驻物理内存的页的集合、其中包含共享页、软页错误与硬页错误的区别、Transition 页,以及从 Working Set 中移出页。  2 3 4 5

  2. Microsoft Learn, PROCESS_MEMORY_COUNTERS_EX2 structure. 关于 WorkingSetSize、PrivateWorkingSetSize、PrivateUsage、SharedCommitUsage 的定义,以及 PagefileUsage 与 PrivateUsage 都表示进程的 Commit Charge。  2 3 4 5

  3. Microsoft Learn, Introduction to page files. 关于页面文件支撑已修改页的换出、系统崩溃转储和 System Commit Limit 的扩大,System Commit Charge 与 Commit Limit 的定义,以及在任务管理器和性能计数器中的测量。  2 3 4

  4. Microsoft Learn, Page State. 关于虚拟页的 Free、Reserved、Committed 各状态,以及 Reserved 页没有关联物理存储、无法访问。  2

  5. Microsoft Learn, VirtualAlloc function. 关于 MEM_RESERVE 与 MEM_COMMIT 的区别、Commit 时会从系统整体的内存与页面文件中收取 Charge,以及实际的物理页可能直到首次访问才分配。  2 3 4 5

  6. Microsoft Learn, How to determine the appropriate page file size for 64-bit versions of Windows. 关于页面文件的大小取决于峰值 Commit Charge 与崩溃转储需求、硬页错误的读取来源不限于页面文件而包括 EXE、DLL 和内存映射文件,以及相关的性能计数器。  2 3 4 5 6

  7. Microsoft Learn, Virtual Address Space. 关于每个进程拥有独立的虚拟地址空间与页表,以及虚拟地址并不就是物理地址。 

  8. Microsoft Learn, Memory Limits for Windows and Windows Server Releases. 关于 32 位进程的用户模式虚拟地址空间通常为 2GB,在 64 位 Windows 上则依 IMAGE_FILE_LARGE_ADDRESS_AWARE 的有无而为 2GB 或 4GB。 

  9. Microsoft Learn, SetProcessWorkingSetSize function. 关于 Working Set 的最小值与最大值并不保证常驻、可以清空 Working Set,以及过大的设置或操作可能使系统性能变差。 

  10. Microsoft Learn, Memory Performance Information. 关于 Windows 的性能计数器、内存管理 API 与任务管理器显示之间的对应关系,以及 Process 的 Working Set、Working Set - Private、Private Bytes 和 System 的 Committed Bytes、Commit Limit。 

  11. Microsoft Learn, MapViewOfFile function. 关于 FILE_MAP_COPY 下所有页都可能变成写时复制,因此在映射的那一刻就会预留 Commit Charge,以保证整个视图都能由页面文件后备。  2

  12. Microsoft Learn, Understanding Node Metrics and Properties in HPC Cluster Manager. 关于 Available Physical Memory 按 Zeroed、Free、Standby 各列表之和计算,以及各个页列表的含义。 

  13. Microsoft Sysinternals, RAMMap. 关于按用途、页列表、进程、优先级、物理页和文件为单位分析 Windows 物理内存使用量的功能。  2

  14. Microsoft Learn, Process.WorkingSet64 Property. 关于 WorkingSet64 以字节为单位返回进程的 Working Set,并对应 Process 的 Working Set 性能计数器。 

  15. Microsoft Learn, Process.PrivateMemorySize64 Property. 关于 PrivateMemorySize64 返回无法与其他进程共享的进程固有内存,并对应 Private Bytes 性能计数器。 

  16. Microsoft Learn, Process.VirtualMemorySize64 Property. 关于 VirtualMemorySize64 返回进程的虚拟内存量,并对应 Virtual Bytes 性能计数器。 

  17. Microsoft Sysinternals, VMMap. 关于按种类分解进程已提交的虚拟内存,并显示分配给它们的物理内存(Working Set)与详细内存映射的功能。  2

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

任务管理器里的“内存”,是应用分配的全部内存吗?
不是。任务管理器里有 Working Set 系列、Private Working Set 系列、Commit Size 等多个内存列,看哪个界面、哪一列,含义都不同。Working Set 是当前位于 RAM 中的页,Private Bytes 或 Commit Size 则是该进程固有的提交量。请不要把某一个“内存”列理解成应用分配的全部容量或泄漏量。
Working Set 和 Private Bytes 有什么区别?
Working Set 是该进程可见、并且当前常驻于物理 RAM 的页量,其中也包含 DLL、内存映射文件等共享页。Private Bytes 是只有该进程使用的已提交内存量,与当前是否常驻 RAM 无关。因此二者不会是同一个值,也不存在固定的大小关系。
任务管理器里的“已提交 18/32GB”,是指已经向页面文件写入了 18GB 吗?
不是。左边是系统整体当前承诺的提交量,右边是系统能够支撑的提交上限。上限大致由 RAM 与页面文件之和决定,但左边那些量并不都位于页面文件上。大多数已提交的页位于 RAM 中,也有一些页从未分配过物理页。另一方面,像 EXE、DLL、内存映射文件这样可以从原文件重新读取的页,Working Set 增加时不一定会让 Private 的提交量增加同样多。
明明还有空闲 RAM,也会出现 OutOfMemory 吗?
会。因为除了物理 RAM 之外,还有其他会导致分配失败的条件:32 位进程的虚拟地址空间不足、缺少连续的空闲地址范围、系统的提交上限、Job Object 或运行时自有的上限等。尤其是运行在 64 位 Windows 上的 32 位进程,如果没有启用 Large Address Aware,用户模式虚拟地址空间通常上限为 2GB。
禁用页面文件会让 Windows 变快吗?
一般来说不能断定会变快。禁用页面文件会降低系统的提交上限,使暂时不用的已修改页更难从 RAM 中换出,还会影响崩溃转储的配置。页面文件的大小,应当在测量峰值提交量和所需的崩溃转储之后再决定,而不是没有依据就禁用的配置项。
Page Faults/sec 很高就说明内存不足吗?
仅凭这一点无法判断。页错误分为可以用 RAM 中的 Standby 页或与其他进程共享中的页解决的软页错误,以及需要从磁盘读取的硬页错误。不要只看 Page Faults/sec 一项,还要把 Pages Input/sec、Page Reads/sec、Available MBytes、磁盘延迟和处理时间放在同一条时间轴上确认。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表