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

   2023-02-09 学习力0
核心提示:一:背景这篇我们来聊一下 PerfView 在协助 WinDbg 分析 Dump 过程中的两个超实用技巧,可能会帮助我们快速定位最后的问题,主要有如下两块:洞察内存泄漏中的静态大集合变量名。验证当前程序的 GC 模式。这里就把经验分享一下,希望让大家少走弯路。二:如何

一:背景

这篇我们来聊一下 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 的输出不约而同。

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

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 使用C#编写一个.NET分析器(一) 使用csv模块的什么方法可以一次性将一行数据写入文件
    使用C#编写一个.NET分析器(一) 使用csv模块的
    译者注这是在Datadog公司任职的Kevin Gosse大佬使用C#编写.NET分析器的系列文章之一,在国内只有很少很少的人了解和研究.NET分析器,它常被用于APM(应用性能诊断)、IDE、诊断工具中,比如Datadog的APM,Visual Studio的分析器以及Rider和Reshaper等等。之前
    03-08
  • 跨语言调用C#代码的新方式-DllExport 跨语言调用本质
    跨语言调用C#代码的新方式-DllExport 跨语言调
    简介上一篇文章使用C#编写一个.NET分析器文章发布以后,很多小伙伴都对最新的NativeAOT函数导出比较感兴趣,今天故写一篇短文来介绍一下如何使用它。在以前,如果有其他语言需要调用C#编写的库,那基本上只有通过各种RPC的方式(HTTP、GRPC)或者引入一层C++
    03-08
  • 我比较了 Go 和 C# 的速度
    我比较了 Go 和 C# 的速度
    我在 Go 和 C# 之间进行了速度比较。我通常使用 C#,但我有机会使用 Go,并且由于传闻 Go 速度很快,所以我实际测量了它。测量内容我在 Go 和 C# 中执行了一个简单的循环和判断过程,以查看整数 2 到 N 是否为质数。来源是Github参考。测量模式 逻辑内核 8 Wi
    03-08
  • [C#]使用 AltCover 获得代码覆盖率 - E2E Test 和 Unit Test
    [C#]使用 AltCover 获得代码覆盖率 - E2E Test
    背景在 CI/CD 流程当中,测试是 CI 中很重要的部分。跟开发人员关系最大的就是单元测试,单元测试编写完成之后,我们可以使用 IDE 或者 dot cover 等工具获得单元测试对于业务代码的覆盖率。不过我们需要一个独立的 CLI 工具,这样我们才能够在 Jenkins 的 CI
  • C#中LINQ的Select与SelectMany函数如何使用 c反应蛋白高说明什么
    C#中LINQ的Select与SelectMany函数如何使用 c反
    本篇内容主要讲解“C#中LINQ的Select与SelectMany函数如何使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#中LINQ的Select与SelectMany函数如何使用”吧!LINQ的Select与SelectMany函数使用Select扩展函
    02-09 linqselect
  • PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏
    PerfView专题 (第三篇):如何寻找 C# 中的 Virt
    一:背景上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏,这种内存泄漏往往是用 C 的 malloc 或者 C++ 的 new 分配而不释放所造成的,这一篇我们来聊一下由 VirtualAlloc 方法造成的泄漏如何去甄别?了解 VirtualAlloc 的朋友肯定说, C# 这种高
    02-09
  • Blazor和Vue对比学习(知识点杂锦3.04):Blazor中C#和JS互操作(超长文)
    Blazor和Vue对比学习(知识点杂锦3.04):Blazo
    C#和JS互操作的基本语法是比较简单的,但小知识点特别多,同时,受应用加载顺序、组件生命周期以及参数类型的影响,会有比较多坑,需要耐心的学习。在C#中调用JS的场景会比较多,特别是在WASM模式下,由于WebAssembly的限制,很多时候,还是需要借助JS去控制D
    02-09
  • 的键">C#怎么使用struct类型作为泛型Dictionary
    本文小编为大家详细介绍“C#怎么使用struct类型作为泛型DictionaryTKey,TValue的键”,内容详细,步骤清晰,细节处理妥当,希望这篇“C#怎么使用struct类型作为泛型DictionaryTKey,TValue的键”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学
  • C#如何实现折半查找算法 彩票查询
    C#如何实现折半查找算法 彩票查询
    本篇内容主要讲解“C#如何实现折半查找算法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#如何实现折半查找算法”吧!折半查找,也叫二分查找,当在一个数组或集合中查找某个元素时,先定位出中间位置元素
    02-09
  • C#如何实现选择排序 c罗
    C#如何实现选择排序 c罗
    本篇内容主要讲解“C#如何实现选择排序”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#如何实现选择排序”吧!选择排序是一种低效的排序算法,大致过程是:遍历数组的每一个元素,先假设0号位置上的元素是最
    02-09
点击排行