0

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

 1 year ago
source link: https://www.cnblogs.com/huangxincheng/p/16611708.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

这篇我们来聊一下 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<string> mybiglist = new List<string>();

        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);
            }
        }
    }
}

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


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

可以看到,这个变量被 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<string> 集合把内存给吃掉了,是不是非常的方便哈。

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

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

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


<Project Sdk="Microsoft.NET.Sdk">
	<PropertyGroup>
		<ServerGarbageCollection>true</ServerGarbageCollection>
		<OutputType>Exe</OutputType>
		<TargetFramework>net6.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
		<Platforms>AnyCPU;x86</Platforms>
	</PropertyGroup>
</Project>

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

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


    internal class Program
    {
        static void Main(string[] args)
        {
            var value = "8392707";

            Enum.TryParse<Test>(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
        }
    }

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK