GC终结标记 SuspendEE 是怎么回事
一:背景
1. 讲故事
写这篇是起源于训练营里有位朋友提到了一个问题,在 !t -special 输出中有一个 SuspendEE 字样,这个字样在 coreclr 中怎么弄的?输出如下:
0:000> !t -special
ThreadCount: 3
UnstartedThread: 0
BackgroundThread: 2
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 4ab0 000001CC44E5C490 2a020 Cooperative 0000000000000000:0000000000000000 000001cc44e520d0 -00001 MTA (GC)
11 2 19d8 000001CC44E84700 21220 Preemptive 0000000000000000:0000000000000000 000001cc44e520d0 -00001 Ukn (Finalizer)
12 3 6668 000001CC44ED4520 2b220 Preemptive 0000000000000000:0000000000000000 000001cc44e520d0 -00001 MTA
OSID Special thread type
0 4ab0 SuspendEE
10 3b6c DbgHelper
11 19d8 Finalizer
哈哈,其实我特别能理解,很多人学了高级调试之后好奇心会爆棚,看啥都想探究底层,有一种技术上的重生,这篇我们就好好聊一聊。
二:WinDbg 分析
1. SuspendEE 标记是什么
这个单词全称为 Suspend Engine Execution, 即 冻结执行引擎 ,那冻结执行引擎的入口方法在哪里呢?这个考验着你对GC运作骨架图的认识,在 coreclr 源码中有一个骨架图,简化后如下:
GarbageCollectGeneration()
{
SuspendEE();
garbage_collect();
RestartEE();
}
garbage_collect()
{
generation_to_condemn();
gc1();
}
上面的 SuspendEE() 即 SOS 中的 SuspendEE 标记的入口函数,接下来我们深入探究下这个方法。
2. SuspendEE 到底做了什么
如果你仔细阅读过 SuspendEE() 方法的源代码,你会发现核心枚举变量是 ThreadType_DynamicSuspendEE,它起到了定乾坤的作用,参考代码如下:
thread_local size_t t_ThreadType;
void ThreadSuspend::SuspendEE(SUSPEND_REASON reason)
{
// set tls flags for compat with SOS
ClrFlsSetThreadType(ThreadType_DynamicSuspendEE);
}
void ClrFlsSetThreadType(TlsThreadTypeFlag flag)
{
t_ThreadType |= flag;
gCurrentThreadInfo.m_EETlsData = (void**)&t_ThreadType - TlsIdx_ThreadType;
}
enum PredefinedTlsSlots
{
TlsIdx_ThreadType = 11 // bit flags to indicate special thread's type
};
enum TlsThreadTypeFlag // flag used for thread type in Tls data
{
ThreadType_DynamicSuspendEE = 0x00000020,
}
从上面的代码中可以看到 t_ThreadType 是一个 C++ 级的线程本地存储,意味着每一个线程都有其备份,同时它也是 SuspendEE 标记的核心来源,如果 m_EETlsData 的第 11号 槽位为 0x20 的时候, SuspendEE 标记就会被成功打下,并且可以通过 gCurrentThreadInfo.m_EETlsData 变量去跟踪来源,有了这么多信息之后,接下来就可以代码验证了。
三:案例验证
1. 一段测试代码
代码非常简单,就是一个简单的手工 GC触发。
internal class Program
{
static void Main(string[] args)
{
Debugger.Break();
GC.Collect();
Console.ReadLine();
}
}
接下来使用 windbg 在入口的 SuspendEE 方法上下断点 bp coreclr!ThreadSuspend::SuspendEE 观察,截图如下:

一旦将 ThreadType_DynamicSuspendEE=0x20 赋值之后,接下来用 windbg 去做个验证。
0:000> x coreclr!*gCurrentThreadInfo*
000001a1`668ee8c0 coreclr!gCurrentThreadInfo = struct ThreadLocalInfo
0:000> dx -id 0,0 -r1 (*((coreclr!ThreadLocalInfo *)0x1a1668ee8c0))
(*((coreclr!ThreadLocalInfo *)0x1a1668ee8c0)) [Type: ThreadLocalInfo]
[+0x000] m_pThread : 0x1a166902e50 [Type: Thread *]
[+0x008] m_pAppDomain : 0x1a166948b40 [Type: AppDomain *]
[+0x010] m_EETlsData : 0x1a1668ee880 [Type: void * *]
0:000> dp 0x1a1668ee880
000001a1`668ee880 00000000`00000000 00000000`00000000
000001a1`668ee890 00000000`00000000 00000000`00000000
000001a1`668ee8a0 00000000`00000000 00000000`00000000
000001a1`668ee8b0 00000000`00000000 00000000`00000000
000001a1`668ee8c0 000001a1`66902e50 000001a1`66948b40
000001a1`668ee8d0 000001a1`668ee880 00000000`00000020
从上面输出可以看到 000001a1668ee8d0+0x8 地址的内容已经被成功种下,相信这时候 !t -special 也能拿到标记了。
0:000> !t -special
Lock
DBG ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 640c 000001A166902E50 2a020 Preemptive 000001A16B0094A8:000001A16B00A5B8 000001a166948b40 -00001 MTA (GC)
11 2 3e50 000001A16692B2D0 21220 Preemptive 0000000000000000:0000000000000000 000001a166948b40 -00001 Ukn (Finalizer)
12 3 6a24 000001A16699F8F0 2b220 Preemptive 0000000000000000:0000000000000000 000001a166948b40 -00001 MTA
OSID Special thread type
0 640c SuspendEE
10 76b0 DbgHelper
11 3e50 Finalizer
那这个 0x20 什么时候被拿掉呢? 这个在源码中也能找到相应的答案,继续 go 运行,输出如下:
void ClrFlsClearThreadType(TlsThreadTypeFlag flag)
{
t_ThreadType &= ~flag;
}
0:012> dp 0x1a1668ee880
000001a1`668ee880 00000000`00000000 00000000`00000000
000001a1`668ee890 00000000`00000000 00000000`00000000
000001a1`668ee8a0 00000000`00000000 00000000`00000000
000001a1`668ee8b0 00000000`00000000 00000000`00000000
000001a1`668ee8c0 000001a1`66902e50 000001a1`66948b40
000001a1`668ee8d0 000001a1`668ee880 00000000`00000000
当然如果你去寻找 sos 的源码实现,也会找到相应的答案。
HRESULT PrintSpecialThreads()
{
...
if (ThreadType & ThreadType_DynamicSuspendEE)
{
type += "SuspendEE ";
}
...
return Status;
}
四:总结
挖掘这个标记的前世今生回头看其实还是挺有意思的,coreclr 居然新增了 m_EETlsData 字段来给 sos 做妥协,哈哈,这彰显了 sos 一等公民的地位。

GC终结标记 SuspendEE 是怎么回事的更多相关文章
- JAVA GC之标记 第五节
JAVA GC之标记 第五节 OK,我们继续昨天最后留下的问题,什么是标记?怎么标记? 第一个问题相信大家都知道,标记就是对一些已死的对象打上记号,方便垃圾收集器的清理. 至于怎么标记,一般有两种方 ...
- JVM(5)之 GC之标记
开发十年,就只剩下这套架构体系了! >>> 堆分为年轻代和年老代.永久代是非堆内存,它又叫做方法区(一般的说法),主要存储已被加载的类信息.常量.静态变量.而该区域在java ...
- [翻译] 编写高性能 .NET 代码--第二章 GC -- 避免使用终结器,避免大对象,避免复制缓冲区
避免使用终结器 如果没有必要,是不需要实现一个终结器(Finalizer).终结器的代码主要是让GC回收非托管资源用.它会在GC完成标记对象为可回收后,放入一个终结器队列里,在由另外一个线程执行队列里 ...
- 【C# .Net GC】清除非托管类型(Finalize终结器、dispose模式以及safeHandler)
总结 1.一般要获取一个内核对象的引用,最好用SafeHandle来引用它,这个类可以帮你管理引用计数,而且用它引用内核对象,代码更健壮 2.托管中生成并引用非托管,一但非托管和托管中的引用断开(托管 ...
- .NET GC机制学习笔记
学习笔记内容来自网络资料摘录http://www.cnblogs.com/springyangwc/archive/2011/06/13/2080149.html 1.GC介绍 Garbage Col ...
- CoreCLR文档翻译 - GC的设计
此文档来源于CoreCLR的BOTR(The Book of the Runtime), 点击打开原文 一切著作权归微软公司所有 GC的设计 作者: Maoni Stephens (@maoni0) ...
- .NET Core GC 的设计
此文章转载自:http://www.cnblogs.com/zkweb/p/6288457.html 作者: Maoni Stephens ( @maoni0) - 2015 提示: 推荐看 The ...
- 托管堆和垃圾回收(GC)
一.基础 首先,为了深入了解垃圾回收(GC),我们要了解一些基础知识: CLR:Common Language Runtime,即公共语言运行时,是一个可由多种面向CLR的编程语言使用的"运 ...
- 【C# .Net GC】垃圾回收算法 应用程序线程运行时,
触发垃圾回收算法的条件 触发垃圾回收的条件 当满足以下条件之一时将发生垃圾回收: 操作系统报告低内存请看(将触发第2代垃圾回收). 这是通过 OS 的内存不足通知或主机指示的内存不足检测出来. 由托管 ...
- Java Hotspot G1 GC的一些关键技术
G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用,作为体验版随着JDK 6u14版本面世,在JDK 7u4版本发行时被正式推出,相 ...
随机推荐
- python重拾基础第三天
本节内容 函数基本语法及特性 参数与局部变量 返回值 嵌套函数 递归 匿名函数 函数式编程介绍 高阶函数 内置函数 1. 函数基本语法及特性 背景提要 现在老板让你写一个监控程序,监控服务器的系统状况 ...
- IEC103设备数据 转 IEC61850项目案例
目录 1 案例说明 1 2 VFBOX网关工作原理 1 3 准备工作 2 4 配置VFBOX网关采集103设备数是 2 5 用IEC61850协议转发数据 4 6 网关使用多个逻辑设备和逻辑节点的方法 ...
- 3568F-Linux应用开发手册
- WIN 11 共享设置
我的电脑 -> 管理 -> 用户 -> 解除 GUEST 禁用 gpedit.msc -> 计算机管理 -> 本地 -> 从网络上访问本机 -> 允许GUES ...
- Linux开机启动自定义脚本
方式一:chkconfig命令 首先编写好自启的脚本 /etc/init.d/test.sh #!/bin/sh # chkconfig: 2345 10 90 # 创建个文件 touch /opt/ ...
- Mysql通过frm和ibd恢复数据库
昨天的考试过程中,有个考点的服务器蓝屏重启后发现Mysql启动不了(5.6.45 x32版本,使用innoDB),重装后无法加载原数据库记录,通过查询资料,通过frm和idb文件成功恢复了数据库记录. ...
- PowerBuilder编程新思维10.5:外传2(PowerPlume下一代开发解决方案)
万里归来年愈少 PB编程新思维10.5:外传2(PowerPlume下一代解决方案) 前言 今天我们就来盘点一下,PB下一代开发的所有技术可能性.所谓下一代开发技术,就是指脱离或半脱离PBVM的应用开 ...
- Swift开发基础06-闭包
Swift的闭包(Closures)是一种将功能块和上下文整合并演示在代码中的一种手段.闭包可以捕获并存储其上下文中的变量和常量.与普遍存在于其他语言的匿名函数(如Python的lambda.Java ...
- 美团VS饿了么,到底谁更胜一筹?
最近啊,收到一个粉丝的投稿,我发现他在美团和饿了么都去面试过. 这俩企业大家应该都经常用吧,咱点外卖的时候,我有时候就琢磨,到底他俩谁更厉害点. 今天咱们就瞅瞅,在面试这块儿谁更难一些. (目前都只有 ...
- Java maven反应堆构建学习实践
Java maven反应堆构建学习实践 实践环境 Apache Maven 3.0.5 (Red Hat 3.0.5-17) 应用示例 示例项目结构 maven示例项目组织结构如下 maven-stu ...