首页 > 系统相关 >PerfView 洞察C#托管堆内存 "黑洞现象"

PerfView 洞察C#托管堆内存 "黑洞现象"

时间:2023-07-24 10:25:05浏览次数:29  
标签:C# PerfView GC 内存 Heap committed allocated size

一:背景

1. 讲故事

首先声明的是这个 黑洞 是我定义的术语,它是用来表示 内存吞噬 的一种现象,何为 内存吞噬,我们来看一张图。

从上面的 卦象图 来看,GCHeap 的 Allocated=852MCommitted=16.6G,它们的差值就是 分配缓冲区=16G,缓冲区的好处就是用空间换时间,弊端就是会实实在在的侵占内存,挤压其他程序的生存空间。

二:黑洞现象

1. 为什么会有黑洞现象

万事皆有因果,今生的是前世种的,换句话说是程序曾经有大量及频繁的创建临时对象,让GC不自主的痉挛,小挛伤神,大挛伤身,所以GC为了避免大挛的发生,就大量的囤积本应该释放掉的内存,目的就是防止未来某个时刻再次有大内存分配的发生。

2. 重现今生的果

我相信因果关系大家都弄清楚了,但口说无凭,还得用代码证明一下不是?为了模拟GC痉挛,上一段测试代码。


    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddAuthorization();
            var app = builder.Build();

            // Configure the HTTP request pipeline.
            app.UseAuthorization();

            app.MapGet("/mytest", (HttpContext httpContext) =>
            {
                return MyTest();
            });

            app.MapGet("/gc", (HttpContext httpContext) =>
            {
                GC.Collect();

                return 1;
            });

            app.Run();
        }

        public static string MyTest()
        {
            List<string> list = new List<string>();

            for (int i = 0; i < 100000000; i++)
            {
                list.Add(i.ToString());
            }

            return "ok";
        }
    }

代码非常简单,每请求一次 /mytest 都会分配一个 1亿 大小 List<string> 数组,而这个 List<string> 又是一个临时对象,后续会被 GC 回收,接下来我们多请求几次来调戏一下 GC,看他如何痉挛,截图如下:

从卦中看,我当前请求了 6 次,内存峰值达到了 12G,因为是临时对象,稍稍有一点回落,但此时已经撑成一个大胖子了,接下来我们用 WinDbg 附加一下,观察下 Allocated 和 Committed 阈值。


0:033> !eeheap -gc

========================================
Number of GC Heaps: 12
----------------------------------------
...
Heap 11 (0000023513f26c10)
generation 0 starts at 23351c3aab8
generation 1 starts at 233484c38e0
generation 2 starts at 233484c1000
ephemeral segment allocation context: none
Small object heap
         segment            begin        allocated        committed allocated size          committed size         
    0233484c0000     0233484c1000     02335c794ad0     023379ad2000 0x142d3ad0 (338508496)  0x31612000 (828448768) 
Large object heap starts at 234384c1000
         segment            begin        allocated        committed allocated size          committed size         
    0234384c0000     0234384c1000     0234384c1018     0234384e2000 0x18 (24)               0x22000 (139264)       
Pinned object heap starts at 234f84c1000
         segment            begin        allocated        committed allocated size          committed size         
    0234f84c0000     0234f84c1000     0234f84c1018     0234f84c2000 0x18 (24)               0x2000 (8192)          
------------------------------
GC Allocated Heap Size:    Size: 0x14f241378 (5622731640) bytes.
GC Committed Heap Size:    Size: 0x2b125c000 (11561975808) bytes.

从卦中看当前已经有 6G 的缓冲区了,为了让缓冲区更夸张,我们故意手工触发一次 GC 即请求 /gc,触发了GC之后,内存从 10G 回落到了 7G 就不再降了,截图如下:

从卦中看,这两个指标就更夸张了,GC 堆只有 1.1M 的对象,但预留了 7.1G 的内存。

这个GC表现不管在 道德 还是 伦理 上都说不通的。

3. 找到前世的因

要想找到前世的因,手段有很多,比如用 WinDbg 观察前世的托管堆,从残留的 Committed - Allocated上就能找到因,也可以使用 PerfView 实时观察,这里我们采用后者来洞察,使用默认的 Command 参数。


PerfView.exe  "/DataFile:PerfViewData.etl" /BufferSizeMB:256 /StackCompression /CircularMB:500 /ClrEvents:GC,Binder,Security,AppDomainResourceManagement,Contention,Exception,Threading,JITSymbols,Type,GCHeapSurvivalAndMovement,GCHeapAndTypeNames,Stack,ThreadTransfer,Codesymbols,Compilation /NoGui /NoNGenRundown /Merge:True /Zip:True collect

采集一段时间后停止采集,接下来双击 GC Heap Net Mem (Coarse Sampling) Stacks 选项再选择 WebApplication1 进程,通过 MaxMetric 指标看到曾经峰值达到了 10.9G,截图如下:

毫无疑问的说,内存峰值的时候必有妖怪,可以将峰值填入到 End 文本框中,然后双击内存占比最高的 System.String[],观察下它是谁分配的,截图如下:

从截图中可以清晰的看到,原来是 Program.MyTest() 造的孽,至此真相大白。

4. 寻求化解之道

化解之道有很多:

  • 修改 GC 模式

简而言之就是将 Server GC 改成 Workstation GC ,参考代码如下:


<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <ServerGarbageCollection>false</ServerGarbageCollection>
  </PropertyGroup>

</Project>

  • 修改 Heap 个数

默认情况一个 cpucore 有一个 heap,我们可以尽量的减少 heap.count 的个数,比如将 12 个改成 2 个。参考代码如下:


{
   "runtimeOptions": {
      "configProperties": {
         "System.GC.HeapCount": 2
      }
   }
}

  • 大事化小

导致今世的 是因为在内存中短时的出现大对象,可以将大对象拆分成多批次的小对象处理,这样可以达到后浪推前浪的的内存复用,从源头上绕过这个问题。

三:总结

内存黑洞 虽不算 CLR 的一个bug,但绝对是 CLR 可优化的一个空间,分析这类问题是需要经验性的,分享出来供后来者少踩坑吧,毕竟在我的分析旅程中至少遇到了3次

标签:C#,PerfView,GC,内存,Heap,committed,allocated,size
From: https://www.cnblogs.com/huangxincheng/p/17576542.html

相关文章

  • Mac版多平台Java开发工具JetBrains IntelliJ IDEA 2023
    JetBrainsIntelliJ是一个多平台的Java开发工具,可以用于Java开发。它可以帮助您在Linux、Windows、Mac和Linux上开发基于Java的应用程序、软件和服务。它还提供了一个跨平台的工具包,可以为开发者提供Java开发者的基础设施设计支持。JetBrainsIntelliJ与Linux有很多相似之处:Java......
  • PR语音转字幕转换插件Speech to Text for Premiere Pro
    在SpeechtoTextforPremierePro(PR语音转字幕转换插件中您可以使用以下各种格式转换:中文(PL/PRC)、英文、日语、韩语、意大利语、葡萄牙语、波兰语、法语、意大利语、荷兰语、英语、西班牙语等。如果您对中文、日语、韩语、葡萄牙语、法语、荷兰语等语言感兴趣,可以在这里找到......
  • 《Spring6核心源码解析》已完结,涵盖IOC容器、AOP切面、AOT预编译、SpringMVC,面试杠杠
    作者:冰河星球:http://m6z.cn/6aeFbs博客:https://binghe.gitcode.host文章汇总:https://binghe.gitcode.host/md/all/all.html源码地址:https://github.com/binghe001/spring-annotation-book沉淀,成长,突破,帮助他人,成就自我。大家好,我是冰河~~提起Spring,可以这么说,Spring几乎......
  • 【原创】在 VBScript 中使用哈希表(Hashtable)
    环境要求WindowsXP及以上。Windows10、Windows11在Windows功能中勾选.NETFramework3.5(包括.NET2.0和3.0)。使用创建一个Hashtable对象:SetoHT=CreateObject("System.Collections.Hashtable")Count属性:返回表中键值对的数量SetoHT=CreateObj......
  • Peachpie升级了,体现PHP跨平台优越性
    在对Peachpie的用法进行了一些工作之后,现在回到了在编译器本身实现的核心功能。目前正在更加接近完成项目的“初衷”—能够运行完整的现实世界的应用程序PHP会话<?phpsession_start();//thisactuallyinitializes$_SESSIONfromISessionserviceecho......
  • Atcoder ARC058E Iroha and Haiku
    题目中的式子转化一下即存在一位\(i\)使得到\(i\)时的后缀和存在\(X+Y+Z,Y+Z,Z\),再发现\(X+Y+Z\le17\),考虑状压。设\(f_{i,j}\)为填了\(i\)个数当前后缀和中存在的数的状态为\(j\)(只存\(0\simX+Y+Z\)的数是否存在)的方案数。考虑转移,则下一位可......
  • linux-centos7.6-gpt-uefi安装
    目录一、需要二、环境vm设置系统环境一、需要安装的系统适用企业服务器磁盘大于2个的场景二、环境添加硬盘,挂载硬盘环境:linuxcentos7.6vmwareworkstationPro15.5.2build-15785246vm设置系统环境......
  • Github打不开、chrome应用商店打不开终极解决方案-pigcha
    记录一下一个非常好用的工具,便于后期自己进行查找!Pigcha是什么?是⼀款专业的全平台的⽹络代理⼯具,能xx上⽹,能⽅便地针对域名进⾏代理或⾛本地,非常好用。Pigcha使⽤教程下载客户端并注册登录:入口地址官方使用教程:入口地址是联系客服微信:mrnew_注:如果您发现速度未达预......
  • 从互联网到云时代,Apache RocketMQ 是如何演进的?
    作者:隆基2022年,RocketMQ5.0的正式版发布。相对于4.0版本而言,架构走向云原生化,并且覆盖了更多业务场景。消息队列演进史操作系统、数据库、中间件是基础软件的三驾马车,而消息队列属于最经典的中间件之一,已经有30多年的历史。消息队列的发展主要经历了以下几个阶段:第一......
  • 数组去重方法总结(JavaScript 记录)
    在进行项目开发的时候,有时候需要把一些前端的数组进行去重处理,得到一个去重后的数据,然后再进行相关的操作,这也是在前端面试中经常出现的问题数组去重的多种方法:利用ES6Set去重利用for嵌套for,然后splice去重利用indexOf去重利用sort()去重利用对象的属性不能相......