一:背景

写这一篇的目的主要是因为.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个小暂停。

  1. 流程图分析
    为了方便说明,将三阶段画个图如下:

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

初始标记
这个阶段用户线程处于暂停状态,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 type0 55d8 SuspendEE 5 5688 DbgHelper 6 568c Finalizer 8 5730 GC

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

并发标记
这个阶段就是各玩各的,用户线程在正常执行,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 type5 5688 DbgHelper 6 568c Finalizer 8 5730 GC

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

最终标记
等 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 type5 5688 DbgHelper 6 568c Finalizer 8 5730 GC SuspendEE

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

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

0:008> x coreclr!settings
00007ff9e7f82e90 coreclr!WKS::gc_heap::settings = class WKS::gc_mechanisms 0:008> dt coreclr!WKS::gc_heap::settings 00007ff9e7f82e90
+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 ) 。

C# 后台GC 的前因后果相关推荐

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

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

  2. [翻译]在GC上加入DPAD

    本文90%通过机器翻译,另外10%译者按照自己的理解进行翻译,和原文相比有所删减,可能与原文并不是一一对应,但是意思基本一致. 译者水平有限,如果错漏欢迎批评指正 译者@Bing Translator ...

  3. CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

    在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分,下面一些概念目前尚未有公开的文档和书籍讲到. 为了分析这部分我花了一个多月的时间,期间也多次向Cor ...

  4. CoreCLR文档翻译 - GC的设计

    此文档来源于CoreCLR的BOTR(The Book of the Runtime), 点击打开原文 一切著作权归微软公司所有 GC的设计 作者: Maoni Stephens (@maoni0) ...

  5. CoreCLR源码探索(三) GC内存分配器的内部实现

    在前一篇中我讲解了new是怎么工作的, 但是却一笔跳过了内存分配相关的部分. 在这一篇中我将详细讲解GC内存分配器的内部实现. 在看这一篇之前请必须先看完微软BOTR文档中的"Garbage ...

  6. gc垃圾收集器 与gc算法_GC解释:收集器概述

    gc垃圾收集器 与gc算法 当前版本的HotSpot JVM包括三种类型的垃圾收集器: –串行收集器 –并行收集器 –多数同时收集者 它们都是世代的,这意味着它们利用了堆的划分方式 . 垃圾收集器负责 ...

  7. GC解释:收集器概述

    当前版本的HotSpot JVM包括三种类型的垃圾收集器: –串行收集器 –并行收集器 –多数同时收集者 它们都是世代的,这意味着它们利用了堆的划分方式 . 垃圾收集器负责三个主要操作: –查找不再使 ...

  8. Android中GC的触发时机和条件

    本文分析基于Android R(11)源码 Java对象的创建由Allocator负责,回收由Collector负责.从Android O开始,对于前台应用默认的GC Collector是CC(Con ...

  9. [翻译] 编写高性能 .NET 代码--第二章 GC -- 配置选项

    配置选项 在基于"less rope to hang yourself with"思想下,.NET 框架没有给开发提供很多太多的配置选项.但在大多数情况下,GC会跟你的硬件配置,及 ...

最新文章

  1. hashmap的get查找过程
  2. 抛开 Spring 去理解 IOC 思想:原来 IOC 容器这么简单
  3. 西藏最大云计算数据中心明年投入试运营
  4. ElementUIz中el-checkbox-group多选框组的使用,怎样设置一组选项与获取所选值
  5. 官博翻译 | .NET Core 即 .NET 的未来
  6. python实例化对象是什么意思_请帮我理解python对象的实例化.
  7. MAC系统下 win7虚拟机上网应该怎么设置啊
  8. 2022-2027年中国新能源车电控电机市场竞争态势及行业投资前景预测报告
  9. 404 not found是什么意思
  10. powershell_符号链接(symbolic)的创建/重新指向设定Target/与快捷方式的区别/符号链接的应用/onedrive任意文件(夹)同步问题/git管理和符号链接\辨识符号链接函数
  11. 关于TXT转CHM的完整解决方案
  12. 面对台风“烟花”,旅行延误会如何赔付?
  13. 安卓手机怎么格式化_windows10系统与安卓手机怎么共享文件
  14. 插画师如何确定自己的风格?教你如何一步步找到自己绘画风格!
  15. 背景扣除matlab_基于背景减法的目标检测在Matlab中的实现方法
  16. 简单的技能Buff系统
  17. 如何修改HBulider X注释字体的颜色
  18. 基于RxJava2.0+Retrofit2.0超大文件分块(分片)上传(带进度)
  19. I + I2C = I3C:这个附加的“I”是什么?
  20. 重载与重写、重用与重构

热门文章

  1. 使用mongoDB的一些新的(mongoDB报错解决)
  2. 【分享一个动漫拼图项目】
  3. Oracle存储过程
  4. 数据传输服务包年包月_包年包月转按月付费
  5. 130242014013+杨俊杰+第3次实验
  6. html语言左对齐是什么,html - 如何左对齐标签?
  7. maya python 的简单使用
  8. vscode常用插件-Auto Close Tag
  9. 开源移动设备管理(MDM)解决方案的利与弊
  10. 如何实现调用阿里云三方的api