PerfView专题 (第七篇):如何洞察触发 GC 的 C# 代码?

news/2024/5/17 18:18:58/文章来源:https://www.cnblogs.com/huangxincheng/p/16609584.html

一:背景

上一篇我们聊到了如何用 PerfView 洞察 GC 的变化,但总感觉还缺了点什么? 对,就是要跟踪到底是什么代码触发了 GC,这对我们分析由于 GC 导致的 CPU 爆高有非常大的参考价值,在以前我都是用 WinDBG 来实现,但这玩意需要做一些侵入性操作,实战起来不是那么丝滑,虽然有可以录制功能的 TTD,所以需寻找完美的解决方案,在此可以借助一下 PerfView 。

二:如何洞察

1. 一个小案例

为了方便讲解,先上一段简单的测试代码,不断的往 List 中塞入数据,可以实现不断的 GC 触发。


namespace ConsoleApp6
{internal class Program{static void Main(string[] args){Task.Run(Alloc1);Console.ReadLine();}static void Alloc1(){List<string> list = new List<string>();var rand = new Random();for (int i = 0; i < int.MaxValue; i++){list.Add(string.Join(",", Enumerable.Range(1, 1000)));Console.WriteLine(i);}}}
}

2. WinDbg 拦截

用 windbg 拦截的话非常简单,只需要找到 CoreCLR 中触发 GC 的入口方法,然后下一个断点,再用 k 查看其线程栈即可。


0:012> bp coreclr!WKS::GCHeap::GarbageCollectGeneration
0:012> g
Breakpoint 0 hit
coreclr!WKS::GCHeap::GarbageCollectGeneration:
00007ffa`7823f8cc 48895c2408      mov     qword ptr [rsp+8],rbx ss:00000000`2321f200=00000000006c49f8
0:008> k# Child-SP          RetAddr               Call Site
00 00000000`2321f1f8 00007ffa`782f59db     coreclr!WKS::GCHeap::GarbageCollectGeneration [D:\a\_work\1\s\src\coreclr\gc\gc.cpp @ 44615] 
01 00000000`2321f200 00007ffa`78337b5d     coreclr!WKS::gc_heap::trigger_gc_for_alloc+0x2b [D:\a\_work\1\s\src\coreclr\gc\gc.cpp @ 16846] 
02 00000000`2321f230 00007ffa`782db675     coreclr!WKS::gc_heap::try_allocate_more_space+0x5c4c1 [D:\a\_work\1\s\src\coreclr\gc\gc.cpp @ 16977] 
03 00000000`2321f290 00007ffa`78247784     coreclr!WKS::gc_heap::allocate_more_space+0x31 [D:\a\_work\1\s\src\coreclr\gc\gc.cpp @ 17447] 
04 (Inline Function) --------`--------     coreclr!WKS::gc_heap::allocate+0x58 [D:\a\_work\1\s\src\coreclr\gc\gc.cpp @ 17478] 
05 00000000`2321f2c0 00007ffa`781d9768     coreclr!WKS::GCHeap::Alloc+0x84 [D:\a\_work\1\s\src\coreclr\gc\gc.cpp @ 43676] 
06 (Inline Function) --------`--------     coreclr!Alloc+0x125 [D:\a\_work\1\s\src\coreclr\vm\gchelpers.cpp @ 228] 
07 00000000`2321f2f0 00007ffa`782cc0d9     coreclr!AllocateString+0x19c [D:\a\_work\1\s\src\coreclr\vm\gchelpers.cpp @ 861] 
08 00000000`2321f390 00007ffa`18782d10     coreclr!FramedAllocateString+0x79 [D:\a\_work\1\s\src\coreclr\vm\jithelpers.cpp @ 2429] 
09 00000000`2321f4e0 00007ffa`1878687d     System_Private_CoreLib!System.Number.Int32ToDecStr+0xb0
0a 00000000`2321f530 00007ffa`1878668a     System_Private_CoreLib!System.String.JoinCore<int>+0x1bd
0b 00000000`2321f7e0 00007ffa`1877bdcc     System_Private_CoreLib!System.String.Join<int>+0x2a
0c 00000000`2321f820 00007ffa`776d2d0e     ConsoleApp10!ConsoleApp10.Program.Alloc1+0xbc [D:\net6\ConsoleApp1\ConsoleApp10\Program.cs @ 24] 
0d 00000000`2321f8a0 00007ffa`776d7716     System_Private_CoreLib!System.Threading.Tasks.Task.InnerInvoke+0x1e
0e 00000000`2321f8d0 00007ffa`776bd7f5     System_Private_CoreLib!System.Threading.Tasks.Task.<>c.<.cctor>b__272_0+0x16
0f 00000000`2321f900 00007ffa`776d2a38     System_Private_CoreLib!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop+0x35
10 00000000`2321f950 00007ffa`776d2943     System_Private_CoreLib!System.Threading.Tasks.Task.ExecuteWithThreadLocal+0x98
11 00000000`2321f9f0 00007ffa`776c6213     System_Private_CoreLib!System.Threading.Tasks.Task.ExecuteEntryUnsafe+0x53
12 00000000`2321fa30 00007ffa`776cdd8a     System_Private_CoreLib!System.Threading.ThreadPoolWorkQueue.Dispatch+0x263
13 00000000`2321fad0 00007ffa`776b278f     System_Private_CoreLib!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart+0x14a
14 00000000`2321fbe0 00007ffa`7830a853     System_Private_CoreLib!System.Threading.Thread.StartCallback+0x3f
15 00000000`2321fc20 00007ffa`781fd43c     coreclr!CallDescrWorkerInternal+0x83
16 00000000`2321fc60 00007ffa`782ebf93     coreclr!DispatchCallSimple+0x80 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 220] 
17 00000000`2321fcf0 00007ffa`78258695     coreclr!ThreadNative::KickOffThread_Worker+0x63 [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 158] 
18 (Inline Function) --------`--------     coreclr!ManagedThreadBase_DispatchInner+0xd [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7321] 
19 00000000`2321fd50 00007ffa`7825859a     coreclr!ManagedThreadBase_DispatchMiddle+0x85 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7365] 
1a 00000000`2321fe30 00007ffa`782583b9     coreclr!ManagedThreadBase_DispatchOuter+0xae [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7524] 
1b (Inline Function) --------`--------     coreclr!ManagedThreadBase_FullTransition+0x2d [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7569] 
1c (Inline Function) --------`--------     coreclr!ManagedThreadBase::KickOff+0x2d [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7604] 
1d 00000000`2321fed0 00007ffa`e9e47034     coreclr!ThreadNative::KickOffThread+0x79 [D:\a\_work\1\s\src\coreclr\vm\comsynchronizable.cpp @ 230] 
1e 00000000`2321ff30 00007ffa`e9f9d0d1     KERNEL32!BaseThreadInitThunk+0x14
1f 00000000`2321ff60 00000000`00000000     ntdll!RtlUserThreadStart+0x21

从输出中可以看到,当前触发的GC的操作是由于 ConsoleApp10!ConsoleApp10.Program.Alloc1+0xbc 方法所致,原因是由于0代阈值用完,但这种调试 CoreCLR 源码的方式终究还是侵入性较大,那有没有自动帮我收集分配 线程调用栈 的工具呢? 这就需要借助 PerfView 啦。

3. PerfView 拦截

Windows 系统内置强大的事件跟踪机制(ETW)再次显示出了威力,我们可以用 PerfView 去拦截 GC 的触发事件,并同时记录此时事件触发的调用栈,而且对应用程序的开销非常小。

接下来我们在 Provider Browser 对话框中选择 GCKeyword 选项,

然后再配上一个记录调用栈参数 @StacksEnabled=true, 最后的结果就是:


Microsoft-Windows-DotNETRuntime:GCKeyword:Always:@StacksEnabled=true

配置好之后就可以 Start Collection 了,收集好之后,我们点击面板上的 Events 项,然后输出 GC 关键词,寻找 GC 相关的事件,这里我们看下 GC/Start 事件,如下图所示:

从截图看,非常舒服,清晰的记录着每一个 GC 的详细情况。


HasStack="True" ThreadID="12,912" ProcessorNumber="3" Count="67" Reason="AllocSmall" Depth="0" Type="NonConcurrentGC" ClrInstanceID="8" ClientSequenceNumber="0" 

接下来在 Time MSec 列上点击右键选择 Open Any Stacks 打开触发这个 GC 的调用栈代码。

从图中可以非常清晰的看到,Alloc1() 方法触发了 GC 并清晰记录 ETW 事件的全过程,真是太强大,太令人兴奋了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_379889.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat源码分析--类加载器

Tomcat类加载器结构上图是Tomcat文档中所展示的Tomcat类加载结构。在这个结构中Bootstartap和System的类加载器由java虚拟机实现。common类加载器由Tomcat容器实现,它对 Tomcat 内部类和所有 Web 应用程序都是可见的。此类加载器搜索的位置$CATALINA_BASE/conf/catalina.prope…

Java概述

从项目到代码找工作前的整个学习体系(学会这些东西去解决问题,不单单去学这些东西)JavaSE知识图Java语言跨平台原理 Java语言特点完全面向对象:Java支持封装,继承,多态,面向对象编程,让程序更好的达到高内聚,低耦合的标准。 支持分布式: 健壮型:Java强类型机制,异常…

【Oracle partition by 实现字段去重】

1.利用开窗函数可以实现字段分组去重,其中要注意几点:需要外套查询语句,不然无法识别ROWNUM ,也就是 row_number 注意 分组排序方式,比如这里排序是 s_id 降序排列,以s_code 进行分组,如果有两行数据的 s_code 相同,那么rownum =1 的时候就 是只取第一组,即 s_id最大的…

Vulnhub | DC-4

信息搜集 通过mac地址找真实IP地址 真实IP为192.168.0.151,开放端口80,22 爆破密码 访问后是一个登录框,爆破尝试 admin/happy访问后是一个登录框,爆破尝试 admin/happy 命令执行 登陆后有一个命令执行选项,抓包修改参数可以执行任意命令 反弹shell失败了 找一找敏感文件 ca…

PowerShell教程 - 编程结构(Program Struct)- 第三部分

更新记录 转载请注明出处。 2022年8月21日 发布。 2022年8月18日 从笔记迁移到博客。预定义变量 预定义的布尔值 $True $False预定义变量 预定义变量 描述(Description) $^ 表示当前会话的使用过的最后一条命名的最前部分 $$ 表示当前会话的使用过的最后一…

压测工具 Locust

Locust是一款易于使用的分布式负载测试工具,完全基于事件,即一个locust节点也可以在一个进程中支持数千并发用户,不使用回调,通过gevent使用轻量级过程(即在自己的进程内运行)一、认识Locust 定义 Locust是一款易于使用的分布式负载测试工具,完全基于事件,即一个locust…

#{}和${}的区别是什么

#{}和${}的区别是什么 动态 sql 是 MyBatis 的主要特性之一,在 mapper 中定义的参数传到 xml 中之后,在查询之前 MyBatis 会对其进行动态解析。MyBatis 为我们提供了两种支持动态 sql 的语法:#{} 以及 ${}。 区别 1)#{}是预编译处理,$ {}是字符串替换。 2)MyBatis在处理#…

Mybatis组件介绍

核心组件 SqlSessionFactoryBuilder SqlSessionFactoryBuilder的作用就是通过XML或者Java代码来建造一个工厂(SqlSessionFactory),并且可以通过它建造多个这样的工厂。一旦完成建造工厂的任务,我们就应该废弃它,回收空间。所以它的生命周期只存在方法局部,完成工厂的建造即…

JAVA入门2022年8月19日

第一节 1.注释是什么写在程序中对程序进行解释说明的文字。 2.java中书写注释的方法有几种,各自有什么不同// 单行注释/* */ 多行注释/** */ 文档注释 3.注释有什么特点不进行编译,不影响程序的执行 4.注释的快捷键是怎么样的 第二节1.字面…

vue的生命周期

一、Vue 的生命周期 一、Vue 的生命周期流程图二、Vue 生命周期的具体    生命周期 描述beforeCreate 组件实例被创建之初created 组件实例已经完成创建beforeMount 组件挂载之前mounted 组件挂载到实例上去之后beforeUpdate 组件数据发生变化,更新之前updated 组件数据更新…

spring源码学习笔记1——解析xml生成BeanDefinition的过程解析

spring源码学习笔记1——解析xml生成BeanDefinition的过程解析 一丶Spring解析Xml生成BeanDefinition的流程 1.指定xml路径 解析xml首先需要知道xml的位置,如下我们构造了ApplicationContext ApplicationContext context =new ClassPathXmlApplicationContext("bean.xml&…

IOC

介绍 什么是SpringIOC,就是把每一个bean(实体类)与bean(实体类)之间的关系交给第三方容器进行管理。关键类BeanFactory IOC的顶层容器,描述了IOC的规范。 BeanFactory是一个接口,是Spring中工厂的顶层规范,IOC的核心接口。 定义了getBean()、containsBean()等管理Bean的通用…

JUC进阶

JUC进阶 wait和sleep的区别sleep是Thread的静态方法,wait是Object方法sleep不会释放锁,它也不需要占用锁,wait会释放锁但调用它的前提是当前线程占有锁wait必须在同步代码块中Lock锁public class LockTest { public static void main(String[] args) { Ticket t…

Spring 03: 基于xml的构造方法注入

构造方法注入具体有3种注入方式:通过构造方法的 a.参数名称注入 b.参数下标注入 c.默认参数顺序注入参数名称注入School实体类package com.example.pojo03;public class School {private String name;private String address;@Overridepublic String toString() {return &…

Vmware 安装CentOS 7

Vmware 安装CentOS 7 创建虚拟机 1、新建虚拟机,选择自定义(高级),下一步。其他默认下一步。选择操作系统Linux,CentOS 7 64位,下一步。输入主机名称,虚拟机存储位置。 2、输入内核数量3、输入内存大小,下一步,其他默认下一步。4、指定磁盘大小,下一步5、自定义硬件,…

深度学习基础课:课程介绍

大家好~我开设了“深度学习基础班”的线上课程,带领同学从0开始学习全连接和卷积神经网络,进行数学推导,并且实现可以运行的Demo程序 本文为第一节课:课程介绍的复盘文章深度学习基础课:课程介绍 大家好~我开设了“深度学习基础班”的线上课程,带领同学从0开始学习全连接…

一台服务器​最大并发 TCP 连接数多少

一台服务器​最大并发 TCP 连接数多少 入门小站 入门小站 2022-07-06 22:10 发表于湖北收录于合集#Linux485个 #tcp4个首先,问题中描述的65535个连接指的是客户端连接数的限制。在tcp应用中,server事先在某个固定端口监听,client主动发起连接,经过三路握手后建立tcp连接。那…

js的原型

prototype 概述:所有的函数都拥有一个属性 这个属性称为prototype 他是一个对象空间(里面就可以存放对应的数据)他被称为显式原型从上述代码 大家可以看到对应的构造函数的prototype和对应的实例对象的 __proto__ 是相等,那么也就证明了对应俩个内容其实是一个对象。那么我…

Condition介绍

Condition Condition是一种多线程通信工具,表示多线程下参与数据竞争的线程的一种状态,主要负责多线程环境下对线程的挂起和唤醒工作。 方法 // ========== 阻塞 ========== // 造成当前线程在接到信号或被中断之前一直处于等待状态。 void await() throws InterruptedExcept…

解决goland在mac m1下无法调试问题

背景 新电脑mac m1 goland调试抛出异常 异常信息 第一次异常信息 could not launch process: can not run under Rosetta, check that the installed build of Go is right for your CPU architecture 原因是goland版本安装错了. 下载地址:https://www.jetbrains.com/zh-cn/go…