一:背景

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

二:为什么要引入后台GC

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

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

这种 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 work
do_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 up
gc1();
}
} gc1()
{
background_mark_phase();
background_sweep();
}

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

2. 流程图分析

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

特别声明:阶段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 -special
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 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 type
0 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> bp coreclr!WKS::gc_heap::revisit_written_pages
0:008> g
coreclr!WKS::gc_heap::revisit_written_pages:
0:008> !t -special
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 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 type
5 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 -special
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 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 type
5 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,好了,本篇就聊这么多,希望对你有帮助。

聊一聊 C# 后台GC 到底是怎么回事?的更多相关文章

  1. 页面通过Jquery取值然后传值到后台显示underfined是怎么回事?

    页面通过Jquery取值然后传值到后台显示underfined是怎么回事? 一般情况下第一个如果用jQuery取值的,末尾要用val(),如果用$符号取值的,末尾要加上val. eg: busines ...

  2. 一起看下MySQL的崩溃恢复到底是怎么回事

    目录 回顾 思考一个问题 checkponit机制 Checkpoint的种类及触发条件 LSN 推荐阅读 本文稍微有点晦涩.但是看过之后你就能Get到MySQL的崩溃恢复到底是怎么做的! 文章公号 ...

  3. 聊一聊 JVM 的 GC

    原文链接:https://www.changxuan.top/?p=1457 引言 JVM 中的 GC 在技术博客中应该算是个老生常谈的话题,网络上也存在着许多质量参差不齐的文章,可以看出来大都是&q ...

  4. 计算机网络中的TCP/UDP协议到底是怎么回事(二)

    上一篇博客阐述了TCP/IP五层网络结构模型以及一些关于TCP.UDP的基础知识,这篇博客会接着写一些关于TCP拥塞控制的算法以及对TCP中常有的疑问进行解答. TCP拥塞控制 首先了解几个概念,为下 ...

  5. 计算机网络中的TCP/UDP协议到底是怎么回事(一)

    TCP/IP五层网络结构模型 物理层:物理层建立在物理通信介质的基础上,作为系统和通信介质的接口,用来实现数据链路实体间透明的比特 (bit) 流传输.只有该层为真实物理通信,其它各层为虚拟通信 数据 ...

  6. CSS浮动属性Float到底什么怎么回事,下面详细解释一下

    float 是 css 的定位属性.在传统的印刷布局中,文本可以按照需要围绕图片.一般把这种方式称为“文本环绕”.在网页设计中,应用了CSS的float属性的页面元素就像在印刷布局里面的被文字包围的图 ...

  7. 移动端click事件延迟300ms到底是怎么回事,该如何解决?

    不管在移动端还是PC端,我们都需要处理用户点击,这个最常用的事件.但在touch端click事件响应速度会比较慢,在较老的手机设备上会更为明显(300ms的延迟). 问题由来 这要追溯至 2007 年 ...

  8. Spring 事务 readOnly 到底是怎么回事?

    Spring的事务经常会有这样的配置: 1 <tx:method name="search*" read-only="true" /> 或者这样的注 ...

  9. UTC时间和各个地区的时间到底是怎么回事

    就不分析了,直接写结论 1.同一个时间点全球各地的时间戳是一致的 2.同一个时间戳在不同的时区对应不同的时间   依北京时间为例: 当前时间为 Tue Jan 23 2018 19:02:11 GMT ...

随机推荐

  1. Spring事务源码解读

    一.Spring事务使用 1.通过maven方式引入jar包 <dependency> <groupId>com.alibaba</groupId> <art ...

  2. MinIO学习

    1.Minio及背景 Minio是一个开源的分布式文件存储系统,它基于 Golang 编写,虽然轻量,却拥有着不错的高性能,可以将图片.视频.音乐.pdf这些文件存储到多个主机,可以存储到多个Linu ...

  3. c# DirectoryEntry LDAPS

    参考地址:https://stackoverflow.com/questions/54987776/ldap-connection-error-the-server-is-not-operationa ...

  4. vue大型电商项目尚品汇(前台篇)day05

    紧急更新第二弹,然后就剩下最后一弹,也就是整个前台的项目 一.购物车 1.加入购物车(新知识点) 加入到购物车是需要接口操作的,因为我们需要将用户的加入到购物车的保存到服务器数据库,你的账号后面才会在 ...

  5. 用树莓派USB摄像头做个监控

    [前言] 看着阴暗的角落里吃灰噎到嗓子眼的树莓派,一起陪伴的时光历历在目,往事逐渐涌上心头,每每触及此处,内心总会升腾起阵阵怜悯之情... 我这有两个设备,一个是积灰已久的树莓派,另一个是积灰已久的U ...

  6. PHP odbc查询SQL SERVER数据库带有中文时无返回数据

    近日遇到一个小麻烦当数据库中有中文字符数据 结果odbc_fetch_array后 我用json_encode怎么也得不到数据页面一片空白 我脑子也一片空白后来才知道sqlserver 没有 UTF- ...

  7. 看看CabloyJS是如何实现编辑页面脏标记的

    应用场景 我们在使用Word.Excel时,当修改了内容之后在标题栏会显示脏标记,从而可以明确的告知用户内容有变动.此外,如果在没有保存的情况下关闭窗口,系统会弹出提示框,让用户选择是否放弃修改 那么 ...

  8. C#/VB.NET 在Word转PDF时生成目录书签

    当我们在转换Word文档到PDF格式时,想保留Word文档的标题作为PDF书签,那么应该如何操作呢?那么本文将以C#及VB.NET代码为例,介绍如何在Word转PDF时生成目录书签.下面是具体方法和步 ...

  9. 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具

    上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...

  10. 互联网公司实行目标管理(OKR)五点原则和基础

    下面从公司文化.组织架构.管理者.落地执行和区别绩效考核五个方面,讲述了如何在公司落地目标管理(OKR),这些是实施OKR的基础,也是原则,虽然写得比较简单,其实是我过去两年多不断观察.实践和摸索的总 ...