聊一聊 C# 后台GC 到底是怎么回事?

一:背景

写这一篇的目的主要是因为.NET领域内几本关于阐述GC方面的书,都是纯理论,所以懂得人自然懂,不懂得人也没法亲自验证,这一篇我就用 windbg + 源码 让大家眼见为实。

二:为什么要引入后台GC

1. 后台GC到底解决了什么问题

解决什么问题得先说有什么问题,我们知道 阻塞版GC 有一个显著得特点就是,在 GC 触发期间,所有的用户线程都被 暂停了,这里的 暂停 是一个统称,画图如下:

15076803221bf99654e9c90ab8e46418.png

这种 STW(Stop The World) 模式相信大家都习以为常了,但这里有一个很大的问题,不管当前 GC 是临时代还是全量,还是压缩或者标记,all in 全冻结,这种简单粗暴的做法肯定是不可取的,也是 后台GC 引入的先决条件。

那 后台GC 到底解决了什么问题?

解决在 FullGC 模式下的 标记清除 回收期间,放飞用户线程。

虽然这是一个很好的 Idea,但复杂度绝对上了几个档次。

三:后台GC 详解

1. 后台 GC代码 骨架图

源码面前,了无秘密,在coreclr 项目的 garbage-collection.md 文件中,描述了 后台GC 的代码流程图。

GarbageCollectGeneration(){SuspendEE();garbage_collect();RestartEE();}garbage_collect(){generation_to_condemn();// decide to do a background GC// wake up the background GC thread to do the workdo_background_gc();}do_background_gc(){init_background_gc();start_c_gc ();//wait until restarted by the BGC.wait_to_proceed();}bgc_thread_function(){while (1){// wait on an event// wake upgc1();}}gc1(){background_mark_phase();background_sweep();}

可以清楚的看到就是在做 标记清除 且核心逻辑都在 background_mark_phase() 函数中,实现了标记的三个阶段: 1.初始标记2.并发标记3.最终标记 , 其中 并发标记 阶段,用户线程是正常运行的,实现了将原来整个暂停 优化到了 2个小暂停。

2. 流程图分析

为了方便说明,将三阶段画个图如下:

92e77a74b31ccb16780be7ee8b3f2540.png

特别声明:阶段2的重启是在 background_sweep() 方法中,而不是 最终标记(background_mark_phase) 阶段。

  1. 初始标记

这个阶段用户线程处于暂停状态,bgc 要做的事情就是从 线程栈终结器队列 中寻找用户根实现引用图遍历,然后再让所有用户线程启动,简化后的代码如下:

void gc_heap::background_mark_phase()
{dprintf(3, ("BGC: stack marking"));GCScan::GcScanRoots(background_promote_callback,max_generation, max_generation,&sc);dprintf(3, ("BGC: finalization marking"));finalize_queue->GcScanRoots(background_promote_callback, heap_number, 0);restart_vm();
}

接下来怎么验证 阶段1 是暂停状态呢?为了方便讲述,先上一段测试代码:

internal class Program{static List<string> list = new List<string>();static void Main(string[] args){Debugger.Break();for (int i = 0; i < int.MaxValue; i++){list.Add(String.Join(",", Enumerable.Range(0, 100)));if (i % 10 == 0) list.RemoveAt(0);}}}

然后用 windbg 在 background_mark_phase 函数下一个断点:bp coreclr!WKS::gc_heap::background_mark_phase 即可。

0:009> bp coreclr!WKS::gc_heap::background_mark_phase
0:009> g
Breakpoint 1 hit
coreclr!WKS::gc_heap::background_mark_phase:
00007ff9`e7bf73f4 488bc4          mov     rax,rsp
0:008> !t -specialLock  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1     55d8 00000000006336B0    2a020 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 MTA (GC) 6    2     568c 0000000000662F40    21220 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (Finalizer) 8    4     5730 0000000000676A90    21220 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 Ukn OSID Special thread type0 55d8 SuspendEE 5 5688 DbgHelper 6 568c Finalizer 8 5730 GC

可以清楚的看到,0号线程显示了 SuspendEE 字样,表示此时所有托管线程处于冻结状态。

  1. 并发标记

这个阶段就是各玩各的,用户线程在正常执行,bgc在后台进一步标记,因为是并行,所以存在 bgc 已标记好的对象引用关系被 用户线程 破坏,所以 bgc 用 reset_write_watch 函数借助 windows 的内存页监控,目的就是把那些脏页找出来,在下一个阶段来修正,简化后的代码如下:

void gc_heap::background_mark_phase()
{disable_preemptive(true);//脏页监控reset_write_watch(TRUE);revisit_written_pages(TRUE, TRUE);dprintf(3, ("BGC: handle table marking"));GCScan::GcScanHandles(background_promote,max_generation, max_generation,&sc);disable_preemptive(false);
}

要想验证此时的用户线程是放飞的,可以在 revisit_written_pages 函数下一个断点即可,使用命令:bp coreclr!WKS::gc_heap::revisit_written_pages

0:008> !t -specialLock  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1     55d8 00000000006336B0    2a020 Cooperative 000000000D1FD920:000000000D1FE120 000000000062d650 -00001 MTA 6    2     568c 0000000000662F40    21220 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (Finalizer) 8    4     5730 0000000000676A90    21220 Cooperative 0000000000000000:0000000000000000 000000000062d650 -00001 Ukn OSID Special thread type5 5688 DbgHelper 6 568c Finalizer 8 5730 GC

看到没有,那个 SuspendEE 神奇的消失了,而且 0 号线程的 GC 模式也改成了 Cooperative,表示可允许操控 托管堆。

  1. 最终标记

等 bgc 在后台做的差不多了,就可以再来一次 SupendEE,将 并发标记 期间由用户线程造成的脏引用进行最终一次修正,修正的数据来源就是监控到的 Windows脏页,代码就不上了,我们聊下怎么去验证阶段二又回到了 SuspendEE 状态?可以在 background_sweep() 函数下一个断点, 命令: bp coreclr!WKS::gc_heap::background_sweep

0:000> bp coreclr!WKS::gc_heap::background_sweep
0:000> g
coreclr!WKS::gc_heap::background_sweep:
00007ff9`e7b7a2e0 4053            push    rbx
0:008> !t -specialLock  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1     55d8 00000000006336B0    2a020 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 MTA 6    2     568c 0000000000662F40    21220 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (Finalizer) 8    4     5730 0000000000676A90    21220 Preemptive  0000000000000000:0000000000000000 000000000062d650 -00001 Ukn (GC) OSID Special thread type5 5688 DbgHelper 6 568c Finalizer 8 5730 GC SuspendEE

哈哈,可以看到那个 SuspendEE 又回来了。

3. 后台GC 只会在 fullGC 模式下吗?

这是最后一个要让大家眼见为实的问题,在gc触发期间,内部会维护一个 gc_mechanisms 结构体,其中就记录了当前 GC 触发的种种信息,可以用 windbg 把它导出来看看便知。

0:008> x coreclr!*settings*
00007ff9`e7f82e90 coreclr!WKS::gc_heap::settings = class WKS::gc_mechanisms
0:008> dt coreclr!WKS::gc_heap::settings 00007ff9`e7f82e90+0x000 gc_index         : 0xb3+0x008 condemned_generation : 0n2+0x00c promotion        : 0n1+0x010 compaction       : 0n0+0x014 loh_compaction   : 0n0+0x018 heap_expansion   : 0n0+0x01c concurrent       : 1+0x020 demotion         : 0n0+0x024 card_bundles     : 0n1+0x028 gen0_reduction_count : 0n0+0x02c should_lock_elevation : 0n0+0x030 elevation_locked_count : 0n0+0x034 elevation_reduced : 0n0+0x038 minimal_gc       : 0n0+0x03c reason           : 0 ( reason_alloc_soh )+0x040 pause_mode       : 1 ( pause_interactive )+0x044 found_finalizers : 0n1+0x048 background_p     : 0n0+0x04c b_state          : 0 ( bgc_not_in_process )+0x050 allocations_allowed : 0n1+0x054 stress_induced   : 0n0+0x058 entry_memory_load : 0x49+0x060 entry_available_physical_mem : 0x00000001`0a50d000+0x068 exit_memory_load : 0

condemned_generation=2 可知当前触发的是 2 代GC,原因是代满了 reason : 0 ( reason_alloc_soh )

四:总结

看的再多还不如实操一遍,如果觉得手工编译 coreclr 源码麻烦,可以考虑下 windbg,好了,本篇就聊这么多,希望对你有帮助。

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

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

相关文章

【BIM入门实战】Revit中的墙体层次以及常见问题解答

一、Revit墙体的层次 1. Revit墙体的层次如图 Revit绘制墙体时,要先选择定位线,可以选核心层中心线,也可以选墙中心线,当墙体为对称时,核心层中心线与墙中心线会重合。 2. 具体层次 1)结构[1]:必须在核心边界内 2)衬底[2]:其他材质基础的材料,如胶合板或石膏板 3…

【系统设计】分布式键值数据库

键值存储 ( key-value store )&#xff0c;也称为 K/V 存储或键值数据库&#xff0c;这是一种非关系型数据库。每个值都有一个唯一的 key 关联&#xff0c;也就是我们常说的 键值对。常见的键值存储有 Redis, Amazon DynamoDB&#xff0c;Microsoft Azure Cosmos DB&#xff0c…

【BIM入门实战】Revit建筑墙体:构造、包络、叠层图文详解

本文主要讲解Revit建筑墙体:构造、包络、叠层。 一、基本墙 第一步: 选择菜单栏的【建筑】选项卡中的【墙】下拉菜单→【属性】面板中切换至基本墙→点击属性面板中的【编辑类型】,弹出如下墙体对话框。 第二步: 选择【复制】按钮→重新进行编辑名称,命名为“外墙-1F-2…

win11 恢复win10开始菜单及任务栏

Windows Registry Editor Version 5.00[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced] "Start_ShowClassicMode"dword:00000001 "TaskbarSi"dword:00000000将上述代码存为reg文件&#xff0c;双击导入注册表。 任务栏…

CentOS安装Tomcat

1. 下载Tomcat安装包&#xff1a; Tomcat官网 解压下载下来的tar.gz至任意目录下&#xff0c;执行命令&#xff1a; Java代码 tar -xzf apache-tomcat-7.0.56.tar.gz 解压后如图&#xff1a; 如果是在windows上&#xff0c;则直接解压zip包到任意目录&…

【BIM+GIS】ArcGIS Pro2.8如何打开Revit模型,BIM和GIS融合?

ArcGIS Pro2.8中,可以直接打开Revit模型(.rvt)项目文件,实现了从数据格式方面BIM与GIS的有机融合,具体操作如下所示: 1. Revit2018模型绘制 打开Revit2018软件,选择【建筑样板】,打开标高1楼层平面,新建一个简单的户型:包括四面墙体、2个门和一扇窗户,如下图所示。…

Edge 开发者沙龙|一小时精通Edge扩展开发

点击蓝字关注我们编辑&#xff1a;Alan Wang排版&#xff1a;Rani Sun微软 Reactor 为帮助广开发者&#xff0c;技术爱好者&#xff0c;更好的学习 .NET Core, C#, Python&#xff0c;数据科学&#xff0c;机器学习&#xff0c;AI&#xff0c;区块链, IoT 等技术&#xff0c;将…

tomcat 开启远程debug

1、linux服务器上tomcat配置startup.sh 文件末尾添加&#xff08;不换行&#xff09;&#xff1a;declare -x CATALINA_OPTS"-server -Xdebug -Xnoagent -Djava.compilerNONE -Xrunjdwp:transportdt_socket,servery,suspendn,address9876"2、eclipse配置&#xff1a;…

【测绘程序设计】坐标反算神器V1.0(附C/C#/VB源程序)

【拓展阅读】:【测绘程序设计】坐标正算神器V1.0(附C/C#/VB源程序) 一、坐标反算原理 ​坐标反算:已知两点坐标,反求边长和方位角,称为坐标反算。 原理坐标系: 计算公式: 二、C#程序实现 1. 界面设计 2

ASP.NET Core 6框架揭秘实例演示[01]: 编程初体验

本篇提供的20个简单的演示实例基本涵盖了ASP.NET Core 6基本的编程模式&#xff0c;我们不仅会利用它们来演示针对控制台、API、MVC、gRPC应用的构建与编程&#xff0c;还会演示Dapr在.NET 6中的应用。除此之外&#xff0c;这20个实例还涵盖了针对依赖注入、配置选项、日志记录…

DBeaverEE 21.1.0安装指南

1、 安装jdk11 2、 配置环境变量 将jdk11安装目录加入path&#xff1a;C:\Program Files\Java\jdk-11.0.10\bin3、 安装DBEE 21.1 4、 将dbeaver-agent文件夹复制到DBEE安装目录 5、将DBEE安装目录下的jre目录删除或改名 6、 修改dbeaver.ini文件&#xff0c;在文件最后添加…

跟风学Docker之四:Docker网络解决方案

2019独角兽企业重金招聘Python工程师标准>>> 跟风学Docker之四&#xff1a;Docker网络解决方案 博客分类&#xff1a; docker 前言&#xff1a;前面的部分一直都是单机跑docker&#xff0c;但实际生产环境不可能只用一台来跑。肯定会用到多台&#xff0c;因为他们都…

【测绘程序设计】坐标方位角推算神器(C#版)

本文讲解利用C#语言实现坐标方位角推算,附源码赠送。 1. 神器效果展示 (1)连接角为左角 (2)连接角为右角 2. 方位角推算原理速递 (1)原理示意图

原型模式——创建型模式

2019独角兽企业重金招聘Python工程师标准>>> 思路&#xff1a; 马上又到找工作的时候了&#xff0c;当我们在准备一份份简历的时候有没有考虑过这样一个问题&#xff1f; 面对不同的工作岗位我们需要准备不同的求职简历&#xff0c;但是这样的几份不同的简历中还是有…

如何获取 ASP.NET Core 当前启动地址?

前言上次&#xff0c;我们介绍了配置ASP.NET Core启动地址的多种方法。那么&#xff0c;如何通过代码方式&#xff0c;获取启动后的地址&#xff1f;WebApplication.Urls 对象使用 WebApplication.Urls.Add 方法可以添加启动地址。那么&#xff0c;使用 WebApplication.Urls 应…

【CASS精品教程】CASS9.1查询功能大全(坐标、长度、面积、方位角)

文章目录 1. 查询指定点坐标2. 查询两点距离及方位3. 查询线长4. 查询实体面积CASS9.1中提供了查询指定点坐标、查询两点距离及方位、查询线长、查询实体面积等查询功能,如下图所示: 本文以动画演示的方式,对以上提到的功能进行讲解。 1. 查询指定点坐标 点击【工程应用】…

自定义smokeping告警(邮件+短信)

前段时间接到公司IT同事需求&#xff0c;帮助其配置smokeping的告警功能&#xff0c;之前配置的姿势有些问题&#xff0c;告警有些问题&#xff0c;现在调试OK&#xff0c;在此将关键配置点简单记录下。 关键的配置项主要有&#xff1a; 定义告警规则并配置将告警信息通过管道交…

selenium 定制启动 chrome 的选项

2019独角兽企业重金招聘Python工程师标准>>> selenium 定制启动 chrome 的选项 博客分类&#xff1a; java 搜索引擎&#xff0c;爬虫 使用 selenium 时&#xff0c;我们可能需要对 chrome 做一些特殊的设置&#xff0c;以完成我们期望的浏览器行为&#xff0c;比如…

平台级 SAAS 架构的基础:统一身份管理系统

业内在用户统一身份认证及授权管理领域&#xff0c;主要关注 4 个方面&#xff1a;集中账号管理&#xff08;Account&#xff09;、集中认证管理&#xff08;Authentication&#xff09;、集中授权管理&#xff08;Authorization&#xff09;和集中审计管理&#xff08;Audit&a…

【ArcGIS Pro微课1000例】0017:ArcGIS Pro 2.8制作炫酷的ETOPO1全球DEM地图

ArcGIS Pro相对于ArcGIS,在制图方面做了很大的提升,做出的地图更加优美,本文讲解基于NOAA的ETOPO1数据全球DEM数据制作炫酷的全球DEM地图,先看效果再教学! 1. 效果展示 全球 澳大利亚大陆