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


    🚀 优质资源分享 🚀

    学习路线指引(点击解锁)知识定位人群定位
    🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
    💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

    一:背景

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

    1. 洞察内存泄漏中的静态大集合变量名。
    2. 验证当前程序的 GC 模式。

    这里就把经验分享一下,希望让大家少走弯路。

    二:如何洞察

    1. 查看静态变量名

    如果有过 dump 分析经验的朋友应该知道,当你历经千辛万苦在 内存泄漏 的dump文件中找到了那个内存泄漏最大的集合,但遗憾的是,你不知道这个 集合 的变量名叫什么?

    为了方便讲述,先上一段测试代码:

    
    namespace ConsoleApp10
    {
        internal class Program
     {
            static void Main(string[] args)
            {
                Task.Run(Alloc1);
    
                Console.ReadLine();
    
            }
            public static List mybiglist = new List();
    
            static void Alloc1()
            {
                var rand = new Random();
    
                for (int i = 0; i < 10000; i++)
                {
                    mybiglist.Add(string.Join(",", Enumerable.Range(1, 1000)));
                    Console.WriteLine(mybiglist.Count);
                }
            }
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    接下来把程序跑起来,终于你找到了那个内存占用最大的 List 集合,代码如下:

    
    0:000> !gcroot -all 0000000002e27038
    
    HandleTable:
        00000000004A13E8 (strong handle)
        -> 000000001A841018 System.Object[]
        -> 000000000284D680 System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]]
        -> 0000000012841038 System.String[]
        -> 0000000002E27038 System.String
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以看到,这个变量被 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 采样占比最大,如图所示:

    到这里第一个问题也就解决了,原来是一个叫 mybiglistList 集合把内存给吃掉了,是不是非常的方便哈。

    2. 查看手工修改的 GC 模式

    在我的 dump 分析之旅中,曾经就遇到过一个案例,需要修改 GC 模式,比如说 并发模式 改成 非并发模式,那改完之后我如何验证呢?

    第一种方式就是通过 x 命令去搜 coreclr 中的符号,比如下面这样:

    
    0:000> x coreclr!GCConfig*
    00007ffa`782763f6 coreclr!GCConfig::s_ConcurrentGC = true
    00007ffa`7827b799 coreclr!GCConfig::s_ServerGC = false
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    虽然可以用 WinDbg 实现,但这种需要生成 dump 或者附加到进程中,那能不能在没有侵入的情况下获取 CoreCLR 当前的 GC 模式呢? 肯定是可以的,这又得需要借助 PerfView 啦, 它的底层逻辑是截获 Runtime/Start 这个 ETW 事件,在这个事件中有一个叫 StartupFlags 枚举,里面就记录着当前的 GC 模式。

    为了方便讲述,在 *.csproj 中修改 GC 的模式为 Server 版,代码如下:

    
    
    	
    		trueServerGarbageCollection>
    		ExeOutputType>
    		net6.0TargetFramework>
    		enableImplicitUsings>
    		enableNullable>
    		AnyCPU;x86Platforms>
    	PropertyGroup>
    Project>
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    接下来启动 PerfView ,点击 Collect -> Collect 启动收集,然后把程序跑起来,停止收集后,我们在 Filter 中输入 Runtime/Start 事件,如果你的列表中没有 StartupFlags 列的话,记得在 Cols 上选择一下哦,截图如下:

    从图中可以看到,当前的 StartupFlags=8392707 ,那这一串数字代表什么意思呢?这就需要到 CoreCLR 中找到它的枚举定义,接下来我们写段代码将它翻译出字符串形式。

    
        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 << 1),
     STARTUP\_LOADER\_OPTIMIZATION\_SINGLE\_DOMAIN = (0x1 << 1),
     STARTUP\_LOADER\_OPTIMIZATION\_MULTI\_DOMAIN = (0x2 << 1),
     STARTUP\_LOADER\_OPTIMIZATION\_MULTI\_DOMAIN\_HOST = (0x3 << 1),
     STARTUP\_LOADER\_SAFEMODE = 0x10,
     STARTUP\_LOADER\_SETPREFERENCE = 0x100,
     STARTUP\_SERVER\_GC = 0x1000,
     STARTUP\_HOARD\_GC\_VM = 0x2000,
     STARTUP\_SINGLE\_VERSION\_HOSTING\_INTERFACE = 0x4000,
     STARTUP\_LEGACY\_IMPERSONATION = 0x10000,
     STARTUP\_DISABLE\_COMMITTHREADSTACK = 0x20000,
     STARTUP\_ALWAYSFLOW\_IMPERSONATION = 0x40000,
     STARTUP\_TRIM\_GC\_COMMIT = 0x80000,
     STARTUP\_ETW = 0x100000,
     STARTUP\_ARM = 0x400000,
     STARTUP\_SINGLE\_APPDOMAIN = 0x800000,
     STARTUP\_APPX\_APP\_MODEL = 0x1000000,
     STARTUP\_DISABLE\_RANDOMIZED\_STRING\_HASHING = 0x2000000
     }
     }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    程序跑起来后,截图如下:

    从图中可以清晰的看到,当前的 GC 模式为 CONCURRENT_GC & SERVER_GC,这和 WinDBG 的输出不约而同。

    好了,本篇就聊这两个超实用的分析技巧,希望对大家有所帮助。

  • 相关阅读:
    ES6中WeakMap和WeakSet
    【硬件相关】RDMA网络类别及基础介绍
    AI智能监控平台EasyCVR+无人机方案:实时全景无死角全方面助力山区安防系统新升级
    iOS开发之机器学习框架MediaPipe(5)
    基于python实现的猿眼电影订票系统
    安装EasyX--图形库--从代码到图形
    Redis高级特性和应用:慢查询、Pipeline、事务、Lua
    【数据库】之MYSQL基本语法
    JAVA城市湖泊信息管理系统计算机毕业设计Mybatis+系统+数据库+调试部署
    一文了解GCC(GNU C)语法
  • 原文地址:https://blog.csdn.net/u012804784/article/details/126463594