在高级调试的旅程中,经常会有一些朋友问我什么是 工作集(内存)
,什么是 提交大小
,什么是 Virtual Size
, 什么是 Working Set
。。。截图如下:
既然有很多朋友问,这些用口头也不怎么好描述,刚好上午有时间就系统的聊一下吧。
可能有些朋友知道,内存中的虚拟地址被划分成了三类。
上面的 预定+提交 就是我们的 Virtual Size,即 Virtual Size = Reserved + Committed
。
当然口说无凭,得要拿出证据,写一个 x86 的 C# 测试代码,参考如下:
static void Main(string[] args)
{
Console.WriteLine("hello world!");
Console.ReadLine();
}
将程序跑起来后用 windbg 附加,使用 !address -summary
将计算出的内存和 Process Explorer
工具显示的 Virtual Size 进行对比,截图如下:
有些较真的朋友可能说:Explorer 显示出的是 163.300
,而 windbg
显示的是 163.281
,为什么还差一点点,其实这是不同工具的统计误差,仅此而已。
有些朋友可能知道,一个程序所占的内存最终会在三个地方落地:
这里的 Workding Set
特指的就是 物理内存条
,由于 Windows 有 MappedFile 这种文件映射(内存共享)机制,所以物理内存条上的内存可以进一步划分为 自己独占的
+ 大家共享的
,可能有些朋友比较蒙,截个图如下:
有了这张图的基础,转化为专业术语就是:
Workding Set
= WS Private
+ WS Shareable
最后我们还是用 Explorer 观察下刚才的 C# 程序,截图如下:
刚才我们说到了内存最终会落地到三个地方,其中一个地方就是 虚拟内存(pagefile)
,简而言之它的作用就是给物理内存打辅助,这个 pagefile.sys 默认是在 C 盘上,截图如下:
有了这些基础,就可以列出一个公式了。
Private Bytes
= WS Private
+ Pages Out (pagefile)
上面的 Pages Out
是我定义的换页内存,这个 Private Bytes
指标在分析内存泄露的场景下特别有用,它能够准备的洞察当前程序是否存在大量的 Pages Out(换页内存)
。
为了方便演示出现了大量的换页内存,写一个不断灌数据的例子。
internal class Program
{
static void Main(string[] args)
{
var list = new List<string>();
for (int i = 0; i < 100000000; i++)
{
list.Add(string.Join(",", Enumerable.Range(0, 100000)));
if (i % 10000 == 0) { Console.WriteLine($"i={i}"); }
}
Console.WriteLine("成功!");
Console.ReadLine();
}
}
将程序跑起来后,截图如下:
根据刚才的计算公式:Pages Out = Private Bytes - WS Private
,可以得知大概有 29G 不得不存放在 pagefile 中。
本来想用 wmic pagefile get /value
看一下当前机器的虚拟内存占用,发现有时候不准,我也没太深究了,输出如下:
C:\Users\Administrator>wmic pagefile get /value
AllocatedBaseSize=49464
Caption=C:\pagefile.sys
CurrentUsage=1473
Description=C:\pagefile.sys
InstallDate=20230807095038.481750+480
Name=C:\pagefile.sys
PeakUsage=1640
Status=
TempPageFile=FALSE
不过可以看到,这个 pagefile.sys 已经从刚开始的 4.8G 暴涨到 49G 了,其中一大半都被我的程序吞掉了。
这个也是很多朋友会问的,WS Shareable
和 WS Shared
到底有什么区别,从字面意思上看就是:一个可被多个进程共享的内存页集合中,当前已经被共享的内存页集合。
可能这么说大家有点懵逼,不过没关系,可以借助 VMMap 工具观察。
从图中可以看到 Shareable=104k
,而 Shared=0k
,这是什么意思呢? 由于 ConsoleApp6.exe 是文件映射到内存的,占用了 104k 的物理内存,此时没有其他进程共享这一块物理内存,所以此时为Shared=0,要想把这里的 Shared 也给填充起来,最简单的办法就是开启多个ConsoleApp6实例。
接下来反复点击 ConsoleApp6 生成多个实例,再次使用 VMMap 观察,截图如下:
我尽最大努力通过多个观察工具用眼见为实的方式把这几个内存指标系统的说了一下,希望大家对这几个术语不再迷茫,以后有人问类似问题就可以把这篇丢过去,减轻了你我负担…