PerfView专题 (第八篇):洞察 C# 内存泄漏之寻找静态变量名和GC模式 ...

打印 上一主题 下一主题

主题 897|帖子 897|积分 2691

一:背景

这篇我们来聊一下 PerfView 在协助 WinDbg 分析 Dump 过程中的两个超实用技巧,可能会帮助我们快速定位最后的问题,主要有如下两块:

  • 洞察内存泄漏中的静态大集合变量名。
  • 验证当前程序的 GC 模式。
这里就把经验分享一下,希望让大家少走弯路。
二:如何洞察

1. 查看静态变量名

如果有过 dump 分析经验的朋友应该知道,当你历经千辛万苦在 内存泄漏 的dump文件中找到了那个内存泄漏最大的集合,但遗憾的是,你不知道这个 集合 的变量名叫什么?
为了方便讲述,先上一段测试代码:
  1. namespace ConsoleApp10
  2. {
  3.     internal class Program
  4.     {
  5.         static void Main(string[] args)
  6.         {
  7.             Task.Run(Alloc1);
  8.             Console.ReadLine();
  9.         }
  10.         public static List<string> mybiglist = new List<string>();
  11.         static void Alloc1()
  12.         {
  13.             var rand = new Random();
  14.             for (int i = 0; i < 10000; i++)
  15.             {
  16.                 mybiglist.Add(string.Join(",", Enumerable.Range(1, 1000)));
  17.                 Console.WriteLine(mybiglist.Count);
  18.             }
  19.         }
  20.     }
  21. }
复制代码
接下来把程序跑起来,终于你找到了那个内存占用最大的 List 集合,代码如下:
  1. 0:000> !gcroot -all 0000000002e27038
  2. HandleTable:
  3.     00000000004A13E8 (strong handle)
  4.     -> 000000001A841018 System.Object[]
  5.     -> 000000000284D680 System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]]
  6.     -> 0000000012841038 System.String[]
  7.     -> 0000000002E27038 System.String
复制代码
可以看到,这个变量被 HandleTable 所持有,从经验上来说其实就是一个 static 变量,现在我们迫切需要知道这个变量名叫什么,因为离真相真的咫尺之遥了。。。
如果你没有汇编基础,我敢打赌你肯定在 WinDBG 中找不到这个变量名。 那有没有快捷的方式显示变量名呢? 肯定是可以的,这就需要借助 PerfView 。
接下来点击菜单的 Memory -> Take Heap Snapshot From Dump 按钮,弹出如下对话框,输入 dump 文件以及 output 地址,截图如下:

接下来点击 Dump GC Heap 让 PerfView 从 ConsoleApp10.dmp 中采样生成 *.gcdump 文件,接下来点击 Heap Stacks -> RefTree ,通过 Inc% 可以观察到 [static vars] 下的 mybiglist 采样占比最大,如图所示:

到这里第一个问题也就解决了,原来是一个叫 mybiglist 的 List 集合把内存给吃掉了,是不是非常的方便哈。
2. 查看手工修改的 GC 模式

在我的 dump 分析之旅中,曾经就遇到过一个案例,需要修改 GC 模式,比如说 并发模式 改成 非并发模式,那改完之后我如何验证呢?
第一种方式就是通过 x 命令去搜 coreclr 中的符号,比如下面这样:
  1. 0:000> x coreclr!GCConfig*
  2. 00007ffa`782763f6 coreclr!GCConfig::s_ConcurrentGC = true
  3. 00007ffa`7827b799 coreclr!GCConfig::s_ServerGC = false
复制代码
虽然可以用 WinDbg 实现,但这种需要生成 dump 或者附加到进程中,那能不能在没有侵入的情况下获取 CoreCLR 当前的 GC 模式呢? 肯定是可以的,这又得需要借助 PerfView 啦, 它的底层逻辑是截获 Runtime/Start 这个 ETW 事件,在这个事件中有一个叫 StartupFlags 枚举,里面就记录着当前的 GC 模式。
为了方便讲述,在 *.csproj 中修改 GC 的模式为 Server 版,代码如下:
  1. <Project Sdk="Microsoft.NET.Sdk">
  2.         <PropertyGroup>
  3.                 <ServerGarbageCollection>true</ServerGarbageCollection>
  4.                 <OutputType>Exe</OutputType>
  5.                 <TargetFramework>net6.0</TargetFramework>
  6.                 <ImplicitUsings>enable</ImplicitUsings>
  7.                 <Nullable>enable</Nullable>
  8.                 <Platforms>AnyCPU;x86</Platforms>
  9.         </PropertyGroup>
  10. </Project>
复制代码
接下来启动 PerfView ,点击 Collect -> Collect 启动收集,然后把程序跑起来,停止收集后,我们在 Filter 中输入 Runtime/Start 事件,如果你的列表中没有 StartupFlags 列的话,记得在 Cols 上选择一下哦,截图如下:

从图中可以看到,当前的 StartupFlags=8392707 ,那这一串数字代表什么意思呢?这就需要到 CoreCLR 中找到它的枚举定义,接下来我们写段代码将它翻译出字符串形式。
[code]    internal class Program    {        static void Main(string[] args)        {            var value = "8392707";            Enum.TryParse(value, out var result);            var txt = result.ToString().Replace(", ", "\r\n");            Console.WriteLine(txt);        }        [Flags]        enum Test        {            STARTUP_CONCURRENT_GC = 0x1,            STARTUP_LOADER_OPTIMIZATION_MASK = (0x3

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表