科技使人快乐 發表於 2025-11-17 10:03:00

DotMemory系列:4. 如何分析进程的转储文件

<h2 id="一背景">一:背景</h2>
<h3 id="1-讲故事">1. 讲故事</h3>
<p>前面几篇文章说的都是对进程采集 snapshot 文件,但这种方式的前提需要在目标机器上运行 <code>DotMemory</code> 相关组件,这在很多生产环境下很难做到,我知道很多医疗,金融生产环境,部署一个外来文件都需要层层审批,尤其像 dotmemory 这种商业软件,想上去门到没有。。。</p>
<p>目前主流的做法就是生成dump文件拿到线下分析,如果 dotmemory 不集成这块生态,那就是自绝于天地,接下来我们就来一起研究下。</p>
<h2 id="二分析转储文件">二:分析转储文件</h2>
<h3 id="1-测试代码">1. 测试代码</h3>
<p>为了方便演示,我故意模拟一个内存分配的快进快出案例,即在一个方法内分配大量的临时对象,观察内存的上涨情况,参考代码如下:</p>
<pre><code class="language-C#">
internal class Program
{
    static void Main(string[] args)
    {
      ProcessMemoryAllocation();
      Console.ReadLine();
    }

    static void ProcessMemoryAllocation()
    {
      Console.WriteLine("开始分配内存...");

      // 分配 100MB 数组
      long bytes100MB = (long)100 * 1024 * 1024;
      byte[] array100MB = new byte;
      array100MB = 0x01;
      array100MB = 0xFF;
      Console.WriteLine($"✓ 100MB 数组分配成功: {bytes100MB:N0} 字节");

      // 分配 500MB 数组
      long bytes500MB = (long)500 * 1024 * 1024;
      byte[] array500MB = new byte;
      array500MB = 0x02;
      array500MB = 0xFE;
      Console.WriteLine($"✓ 500MB 数组分配成功: {bytes500MB:N0} 字节");

      // 分配 1GB 数组
      long bytes1GB = (long)1024 * 1024 * 1024;
      byte[] array1GB = new byte;
      array1GB = 0x03;
      array1GB = 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");

    }
}

</code></pre>
<p>程序运行起来之后,可以看到当前程序吃了 1.7G 的内存,接下来用 process explorer 抓一个dump,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742602-306076494.png" alt="" loading="lazy"></p>
<h3 id="2-使用-dotmemory-分析">2. 使用 dotmemory 分析</h3>
<p>点击 <code>Import Process Dump</code> 按钮,找到要分析的文件,稍等之后就能看到快速检测台,这里要注意一下,dump文件越大,稍等的时间就越长,也可能等了几十分钟后结果爆了个内存不足,也是无语了,它的底层是通过一个单独的 <code>MemoryDumpConverter.exe</code> 程序来抽取托管堆数据的,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742629-608119083.png" alt="" loading="lazy"></p>
<p>由于程序吃了 1.7G,所以要观察下到底是谁吃掉了,观察 <code>Overview</code> 视图,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742535-977296220.png" alt="" loading="lazy"></p>
<p>从卦中可以看到 .NET 使用了 <code>1.59G</code> 内存,其中活对象是 <code>105k</code> ,看到这个数据很容易让人联想到上一篇说的 <code>碎片化问题</code>, 那到底是不是呢?</p>
<h3 id="3-是堆碎片化吗">3. 是堆碎片化吗</h3>
<p>进入 <code>Inspections</code> 视图,观察 <code>Heap Fragmentation</code> 区域,发现 <code>LOH</code> 上的 free=1.59G,这就让人很无语了,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742536-1970730525.png" alt="" loading="lazy"></p>
<p>既然整条的 segment 都是 free,为什么gc不在上一阶段完整的回收它呢?带着忐忑不安的心情发现是上图中有一串数字 <code>1.59GB occupied by 76 unreachable objects</code>,啊,,, 原来是待回收的垃圾对象呀,但这个对象目前还不是 free 对象呀,为什么要把它当作 free 处理呢? 这种处理方式和传统的 windbg 处理模式完全不一样,需要适应一样,哈哈。</p>
<p>接下来点击这 76 个不可达对象,可以看到是 3 个超大的 <code>byte[]</code> 给吞掉了,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742631-605312629.png" alt="" loading="lazy"></p>
<p>dump 的单个分析我们差不多搞定了,接下来研究下双dump分析。</p>
<h2 id="三-双-dump-增量分析">三. 双 dump 增量分析</h2>
<h3 id="1-测试代码-1">1. 测试代码</h3>
<p>为了能够捕获内存增量,修改了一下代码,在增量的第一和第三阶段各采一个dump文件,分别为 100M+ 和 1.7G+,代码和截图如下:</p>
<pre><code class="language-C#">
static void ProcessMemoryAllocation()
{
    Console.WriteLine("开始分配内存...");

    // 分配 100MB 数组
    long bytes100MB = (long)100 * 1024 * 1024;
    byte[] array100MB = new byte;
    array100MB = 0x01;
    array100MB = 0xFF;
    Console.WriteLine($"1. 100MB 数组分配成功: {bytes100MB:N0} 字节");

    Thread.Sleep(5000);

    // 分配 500MB 数组
    long bytes500MB = (long)500 * 1024 * 1024;
    byte[] array500MB = new byte;
    array500MB = 0x02;
    array500MB = 0xFE;
    Console.WriteLine($"2. 500MB 数组分配成功: {bytes500MB:N0} 字节");

    Thread.Sleep(5000);

    // 分配 1GB 数组
    long bytes1GB = (long)1024 * 1024 * 1024;
    byte[] array1GB = new byte;
    array1GB = 0x03;
    array1GB = 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();
}

</code></pre>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742507-1799471096.png" alt="" loading="lazy"></p>
<p>这两个dump都有了,如何比较增量呢? 做法比较简单,先将两个 dump 分别导入到 workspace 中,然后随意在一个 overview 中选择 <code>Compare with snapshot from another workspace</code>,即跨工作区比较,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742615-1217649314.png" alt="" loading="lazy"></p>
<p>点击完成之后,就可以看到两个 snapshot 宏观对比,并且在左边区域中可以看到增量为 1.49G,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742635-1920550591.png" alt="" loading="lazy"></p>
<p>鸟瞰图并没有解决问题,所以还要点击右上角的 <code>Compare</code> 观察堆上的对象详情,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742500-1608723468.png" alt="" loading="lazy"></p>
<p>卦中有两个指标可以看懂。</p>
<ol>
<li>Objects delta: 快照1 (ObjectsA) 和 快照2 (ObjectB) 之间的数量增量。</li>
<li>Bytes delta: 快照1 (BytesA) 和 快照2 (BytesB) 之间的占用增量。</li>
</ol>
<p>对比之后终于知道,疑似有 4 个超大的 byte[] 吃掉了 1.49G 的内存。</p>
<p>不过很遗憾的是,在快照对比中看不到这 4个byte[] 的具体详情,what a pity 。。。</p>
<p>接下来怎么办呢? 只能单独进 <code>1.59G</code> 的snapshot,在 Types 选项中找到 <code>byte[]</code> ,然后双击进入即可,截图如下:</p>
<p><img src="https://img2024.cnblogs.com/blog/214741/202511/214741-20251117091742517-1946828698.png" alt="" loading="lazy"></p>
<h2 id="四总结">四:总结</h2>
<p>dotmemory 整合了<code>dump</code> 转储体系,如虎添翼,还是那句话,它是技术支持工程师的好助手。</p>
<img src="https://images.cnblogs.com/cnblogs_com/huangxincheng/345039/o_210929020104最新消息优惠促销公众号关注二维码.jpg" width="700" height="300" alt="图片名称" align="center"><br><br>
来源:https://www.cnblogs.com/huangxincheng/p/19231280
頁: [1]
查看完整版本: DotMemory系列:4. 如何分析进程的转储文件