前面几篇文章说的都是对进程采集 snapshot 文件,但这种方式的前提需要在目标机器上运行DotMemory相关组件,这在很多生产环境下很难做到,我知道很多医疗,金融生产环境,部署一个外来文件都需要层层审批,尤其像 dotmemory 这种商业软件,想上去门到没有。。。摘要:前面几篇文章说的都是对进程采集 snapshot 文件,但这种方式的前提需要在目标机器上运行DotMemory相关组件,这在很多生产环境下很难做到,我知道很多医疗,金融生产环境,部署一个外来文件都需要层层审批,尤其像 dotmemory 这种商业软件,想上去门
目前主流的做法就是生成dump文件拿到线下分析,如果 dotmemory 不集成这块生态,那就是自绝于天地,接下来我们就来一起研究下。
1. 测试代码为了方便演示,我故意模拟一个内存分配的快进快出案例,即在一个方法内分配大量的临时对象,观察内存的上涨情况,参考代码如下:
internal classProgram
{
static void Main(string args)
{
ProcessMemoryAllocation;
Console.ReadLine;
}
static void ProcessMemoryAllocation
{
Console.WriteLine("开始分配内存...");
// 分配 100MB 数组
long bytes100MB = (long)100 * 1024 * 1024;
byte array100MB = newbyte[bytes100MB];
array100MB[0] = 0x01;
array100MB[array100MB.Length - 1] = 0xFF;
Console.WriteLine($"✓ 100MB 数组分配成功: {bytes100MB:N0} 字节");
// 分配 500MB 数组
long bytes500MB = (long)500 * 1024 * 1024;
byte array500MB = newbyte[bytes500MB];
array500MB[0] = 0x02;
array500MB[array500MB.Length - 1] = 0xFE;
Console.WriteLine($"✓ 500MB 数组分配成功: {bytes500MB:N0} 字节");
// 分配 1GB 数组
long bytes1GB = (long)1024 * 1024 * 1024;
byte array1GB = newbyte[bytes1GB];
array1GB[0] = 0x03;
array1GB[array1GB.Length - 1] = 0xFD;
Console.WriteLine($"✓ 1GB 数组分配成功: {bytes1GB:N0} 字节");
long totalBytes = bytes100MB + bytes500MB + bytes1GB;
Console.WriteLine($"分配完成!");
Console.WriteLine($"总计分配: {totalBytes:N0} 字节");
Console.WriteLine($"约 {totalBytes / (1024.0 * 1024.0):F2} MB");
Console.WriteLine($"约 {totalBytes / (1024.0 * 1024.0 * 1024.0):F2} GB");
}
}
程序运行起来之后,可以看到当前程序吃了 1.7G 的内存,接下来用 process explorer 抓一个dump,截图如下:
既然整条的 segment 都是 free,为什么gc不在上一阶段完整的回收它呢?带着忐忑不安的心情发现是上图中有一串数字 1.59GB occupied by 76 unreachable objects,啊,,, 原来是待回收的垃圾对象呀,但这个对象目前还不是 free 对象呀,为什么要把它当作 free 处理呢? 这种处理方式和传统的 windbg 处理模式完全不一样,需要适应一样,哈哈。
接下来点击这 76 个不可达对象,可以看到是 3 个超大的 byte给吞掉了,截图如下:
dump 的单个分析我们差不多搞定了,接下来研究下双dump分析。
1. 测试代码为了能够捕获内存增量,修改了一下代码,在增量的第一和第三阶段各采一个dump文件,分别为 100M+ 和 1.7G+,代码和截图如下:
static void ProcessMemoryAllocation
{
Console.WriteLine("开始分配内存...");
// 分配 100MB 数组
long bytes100MB = (long)100 * 1024 * 1024;
byte array100MB = newbyte[bytes100MB];
array100MB[0] = 0x01;
array100MB[array100MB.Length - 1] = 0xFF;
Console.WriteLine($"1. 100MB 数组分配成功: {bytes100MB:N0} 字节");
Thread.Sleep(5000);
// 分配 500MB 数组
long bytes500MB = (long)500 * 1024 * 1024;
byte array500MB = newbyte[bytes500MB];
array500MB[0] = 0x02;
array500MB[array500MB.Length - 1] = 0xFE;
Console.WriteLine($"2. 500MB 数组分配成功: {bytes500MB:N0} 字节");
Thread.Sleep(5000);
// 分配 1GB 数组
long bytes1GB = (long)1024 * 1024 * 1024;
byte array1GB = newbyte[bytes1GB];
array1GB[0] = 0x03;
array1GB[array1GB.Length - 1] = 0xFD;
Console.WriteLine($"3. 1GB 数组分配成功: {bytes1GB:N0} 字节");
Thread.Sleep(5000);
long totalBytes = bytes100MB + bytes500MB + bytes1GB;
Console.WriteLine($"分配完成!");
Console.WriteLine($"总计分配: {totalBytes:N0} 字节");
Console.WriteLine($"约 {totalBytes / (1024.0 * 1024.0):F2} MB");
Console.WriteLine($"约 {totalBytes / (1024.0 * 1024.0 * 1024.0):F2} GB");
Console.ReadLine;
}
这两个dump都有了,如何比较增量呢? 做法比较简单,先将两个 dump 分别导入到 workspace 中,然后随意在一个 overview 中选择Compare with snapshot from another workspace,即跨工作区比较,截图如下:
点击完成之后,就可以看到两个 snapshot 宏观对比,并且在左边区域中可以看到增量为 1.49G,截图如下:
卦中有两个指标可以看懂。
Objects delta: 快照1 (ObjectsA) 和 快照2 (ObjectB) 之间的数量增量。
Bytes delta: 快照1 (BytesA) 和 快照2 (BytesB) 之间的占用增量。
对比之后终于知道,疑似有 4 个超大的 byte 吃掉了 1.49G 的内存。
不过很遗憾的是,在快照对比中看不到这 4个byte 的具体详情,what a pity 。。。
接下来怎么办呢? 只能单独进1.59G的snapshot,在 Types 选项中找到byte,然后双击进入即可,截图如下:来源:opendotnet