.Net析构函数再论(CLR源码级的剖析)
前言
碰到一些问题,发觉依旧没有全面了解完全析构函数。本篇继续看下析构函数的一些引申知识。
概述
析构函数目前发现的总共有三个标记,这里分别一一介绍下。先上一段代码:
internal class Program : IDisposable{
static void Main(string[] args){
StreamReader? streamReader = null;
streamReader = new StreamReader("Test_.dll");
streamReader?.Dispose();
Console.ReadLine();
}
~Program(){
Console.WriteLine("调用了析构函数");
}
public void Dispose(){
this.Dispose();
GC.SuppressFinalize(this);
}
}
这里的析构函数跟Dispose一起混用, ~Program()析构函数会通过Roslyn生成
.method family hidebysig virtual instance void
Finalize() cil managed
{
.override [System.Runtime]System.Object::Finalize
// 代码大小 24 (0x18)
.maxstack 1
IL_0000: nop
.try
{
IL_0001: nop
IL_0002: ldstr bytearray (03 8C 28 75 86 4E 90 67 84 67 FD 51 70 65 ) // ..(u.N.g.g.Qpe
IL_0007: call void [System.Console]System.Console::WriteLine(string)
IL_000c: nop
IL_000d: leave.s IL_0017
} // end .try
finally
{
IL_000f: ldarg.0
IL_0010: call instance void [System.Runtime]System.Object::Finalize()
IL_0015: nop
IL_0016: endfinally
} // end handler
IL_0017: ret
} // end of method Program::Finalize
这里同时需要注意 streamReader?.Dispose();这句话,streamreader实际上继承的是textreader
public class StreamReader : TextReader
{}
所以它调用Dispose的代码是TextReader里面的Dispose:
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
也就是关闭了streamReader流。然后base.Dispose.这个base.Dispose实际上就是它的父类TextReader里面的
public void Dispose()
{
this._streamReader.close();
}
Dispose里面的下面一句代码
GC.SuppressFinalize(this);
它是重点。
GC.SuppressFinalize
1.判断当前类是否有析构函数
如果类里面有析构函数,比如例子里的Program,则会设置MethodTable的成员m_dwFlags
m_dwFlags |= enum_flag_HasFinalizer(0x00100000);
它的设置逻辑是如果存在析构函数,并且当前方法不是接口,不是虚方法,方法的索引小于当前类宗的索引数,当前的方法不是Object.Finlize()。那么说明当前这个类有析构函数,所以需要在当前类的MethodTable上进行操作,也即上面的m_dwFlags位设置。
逻辑代码如下:
//存在析构函数,并且当前方法不是接口,不是虚方法
if (g_pObjectFinalizerMD && !IsInterface() && !IsValueClass())
{
WORD slot = g_pObjectFinalizerMD->GetSlot();
//方法的索引小于当前类宗的索引数,当前的方法不是Object.Finlize()
if (slot < bmtVT->cVirtualSlots && (*bmtVT)[slot].Impl().GetMethodDesc() != g_pObjectFinalizerMD)
{
GetHalfBakedMethodTable()->SetHasFinalizer(); //这个地方就是设置m_dwFlags
//此处省略一万行
}
}
2.调用GC.SuppressFinalize
设置当前类的对象头headerobj|BIT_SBLK_FINALIZER_RUN
当我们调用GC.SuppressFinalize的时候,它会进行判断m_dwFlags或上的enum_flag_HasFinalizer位是否为1,如果位0直接返回,如果为1,则设置对象头。它的判断逻辑如下
if (!obj->GetMethodTable ()->HasFinalizer())//HasFinalizer函数判断m_dwFlags的enum_flag_HasFinalizer位
return;
GCHeapUtilities::GetGCHeap()->SetFinalizationRun(obj);//这里设置当前类的对象头headerobj|BIT_SBLK_FINALIZER_RUN
BIT_SBLK_FINALIZER_RUN定义如下:
#define BIT_SBLK_FINALIZER_RUN 0x40000000
3.对象进行分配空间的时候
设置flags |= GC_ALLOC_FINALIZE
一个对象需要进行空间的分配,当进行空间分配的时候,它会判断当前函数是否包含了析构函数。如果包含了,则设置flags标志最后一位位1.然后在对象分配的时候,把它放入到析构队列里面去。
if (pMT->HasFinalizer())//判断当前类是否包含析构函数
flags |= GC_ALLOC_FINALIZE;//如果包含则设置flags最后一位为1
GC_ALLOC_FINALIZE定义如下:
enum GC_ALLOC_FLAGS
{
GC_ALLOC_NO_FLAGS = 0,
GC_ALLOC_FINALIZE = 1,
GC_ALLOC_CONTAINS_REF = 2,
GC_ALLOC_ALIGN8_BIAS = 4,
GC_ALLOC_ALIGN8 = 8,
GC_ALLOC_ZEROING_OPTIONAL = 16,
GC_ALLOC_LARGE_OBJECT_HEAP = 32,
GC_ALLOC_PINNED_OBJECT_HEAP = 64,
GC_ALLOC_USER_OLD_HEAP = GC_ALLOC_LARGE_OBJECT_HEAP | GC_ALLOC_PINNED_OBJECT_HEAP,
};
当进行对象分配的时候,它会判断falgs最后一位是否为1,如果为1,则把对象放入到析构队列,不为1,则不放入。
CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(newAlloc, size, flags & GC_ALLOC_FINALIZE); //flags & GC_ALLOC_FINALIZE判断falgs最后一位是否为1.
#define CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(_object, _size, _register) do {
//这里的register就是flags & GC_ALLOC_FINALIZE的值,下面的逻辑如果对象为空直接返回,如果不为空则判断flags & GC_ALLOC_FINALIZE是否等于1,如果为零直接返回,如果为1,则调用REGISTER_FOR_FINALIZATION,把对象放入析构队列
if ((_object) == NULL || ((_register) && !REGISTER_FOR_FINALIZATION(_object, _size)))
{
STRESS_LOG_OOM_STACK(_size);
return NULL;
}
以上是析构函数,GC.SuppressFinalize,Dispose的最底层逻辑。当然这里还有很多技术问题需要解决。后面再看。
标记的作用
GC.SuppressFinalize问题来了,它的这些标记有什么用呢?这是一个非常绕的问题,分析下。首先的enum_flag_HasFinalizer标记表示当前类包含了析构函数,GC_ALLOC_FINALIZE标记表示当前的类对象需要填充到析构队列里面去。而BIT_SBLK_FINALIZER_RUN标记是最为重要的,它如果被标记了则表示从析构队列里面溢出,不需要运行这个当前类的析构函数。
在GC的标记阶段标记对象是否存活完成之后,它需要对对象的析构队列进行扫描。如果析构队列(SegQueue)里的对象被标记存活,且它的对象头有
BIT_SBLK_FINALIZER_RUN标志,则表示此对象的析构队列里的对象可以移出了,也就是不运行此对象的析构函数。
//这里的ScanForFinalization是在GCScanRoot之运行的,还有一个从析构函数里面取出
//对象运行析构函数则是GCHeap::GetNextFinalizableObject
CFinalize::ScanForFinalization (promote_func* pfn, int gen, BOOL mark_only_p,
gc_heap* hp)
{
//判断对象头是否标记了BIT_SBLK_FINALIZER_RUN
if ((obj->GetHeader()->GetBits()) & BIT_SBLK_FINALIZER_RUN)
{
//如果标记了,则把这个对象移除到FreeList,也即是空闲的析构列表,不然存在于析构列表中
MoveItem (i, Seg, FreeList);
//然后清除掉此对象头BIT_SBLK_FINALIZER_RUN标志
obj->GetHeader()->ClrBit (BIT_SBLK_FINALIZER_RUN);
}
}
欢迎关注我的公众号:jianghupt,后台回复:dotnet7。获取一套.Net7 CLR源码教程。顶级技术分享,文章首发。

.Net析构函数再论(CLR源码级的剖析)的更多相关文章
- MapReduce的ReduceTask任务的运行源码级分析
MapReduce的MapTask任务的运行源码级分析 这篇文章好不容易恢复了...谢天谢地...这篇文章讲了MapTask的执行流程.咱们这一节讲解ReduceTask的执行流程.ReduceTas ...
- MapReduce的MapTask任务的运行源码级分析
TaskTracker任务初始化及启动task源码级分析 这篇文章中分析了任务的启动,每个task都会使用一个进程占用一个JVM来执行,org.apache.hadoop.mapred.Child方法 ...
- TaskTracker任务初始化及启动task源码级分析
在监听器初始化Job.JobTracker相应TaskTracker心跳.调度器分配task源码级分析中我们分析的Tasktracker发送心跳的机制,这一节我们分析TaskTracker接受JobT ...
- 源码级调试的XNU内核
i春秋翻译小组-FWorldCodeZ 源码级调试的XNU内核 无论你是在开发内核扩展,进行漏洞研究,还是还有其他需要进入macOS / iOS内核,XNU,有时你需要附加调试器.当你这样做时,使用源 ...
- 源码级强力分析hadoop的RPC机制
分析对象: hadoop版本:hadoop 0.20.203.0 必备技术点: 1. 动态代理(参考 :http://weixiaolu.iteye.com/blog/1477774 )2. Java ...
- 关于JAVA中源码级注解的编写及使用
一.注解简介: 1.1.什么是"注解": 在我们编写代码时,一定看到过这样的代码: class Student { private String name; @Override ...
- MySql轻松入门系列——第二站 使用visual studio 对mysql进行源码级调试
一:背景 1. 讲故事 上一篇说了mysql的架构图,很多同学反馈说不过瘾,毕竟还是听我讲故事,那这篇就来说一说怎么利用visual studio 对 mysql进行源码级调试,毕竟源码面前,不谈隐私 ...
- 准备CLR源码阅读环境
微软发布了CLR 2.0的源码,这个源码是可以直接在freebsd和windows环境下编译及运行的,请在微软shared source cli(http://www.microsoft.com/en ...
- 创作gtk源码级vim帮助文档 tags
创作gtk源码级vim帮助文档 tags 缘由 那只有看到源码了.在linux源码上有个网站 http://lxr.linux.no /+trees, 可以很方面的查出相应版本的代码实现,gtk没有. ...
- .NET框架源码解读之准备CLR源码阅读环境
微软发布了CLR 2.0的源码,这个源码是可以直接在freebsd和windows环境下编译及运行的,请在微软shared source cli(http://www.microsoft.com/en ...
随机推荐
- Python asyncio 库源码分析
Python asyncio 库源码分析 前言 本着 「路漫漫其修远兮, 吾将上下而求索」 的精神.终于要开始深入研究 Python 中 asyncio 的源码实现啦. 本文章可能篇幅较长,因为是逐行 ...
- 3. AOP
1. 代理模式 1.1 概念 ① 介绍 二十三种设计模式中的一种,属于结构型模式.它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用.让不 ...
- RLHF技术在智能金融中的应用:提高金融智能化和自动化水平”
目录 引言 随着人工智能技术的不断发展和普及,金融智能化和自动化水平也得到了显著提高.在这个时代,RLHF(Reinforcement Learning with Human Feedback)技术已 ...
- 使用MASA Stack+.Net 从零开始搭建IoT平台 第五章 使用时序库存储上行数据
@ 目录 前言 分析 实施步骤 时序库的安装 解决playload没有时间戳问题 代码编写 测试 总结 前言 我们可以将设备上行数据存储到关系型数据库中,我们需要两张带有时间戳的表(最新数据表 和 历 ...
- 这问题巧了,SpringMVC 不同参数处理机制引发的思考
这个问题非常有趣,不是SpringMVC 的问题,是实际开发中混合使用了两种请求方式暴露出来的. 问题场景 功能模块中,提供两个 Http 服务.一个是列表查询(application/json 请求 ...
- 【Oracle】行转列的函数wm_concat,listagg,xmlagg,pivot以及动态行转列
[Oracle]行转列的几种情况 表的数据如下 朴实无华的函数 1.wm_concat 使用格式: select 分组字段,wm_concat(要转换的列名) from 表名 group by 分组字 ...
- 在langchain中使用带简短知识内容的prompt template
简介 langchain中有个比较有意思的prompt template叫做FewShotPromptTemplate. 他是这句话的简写:"Prompt template that con ...
- RAT蓝队自动化测试框架
RAT蓝队自动化测试框架 介绍 RAT 是根据 MITRE ATT&CK 战术矩阵测试蓝队检测能力的脚本框架,由 python2.7 编写,共有 50 多种不同 ATT&CK 技术点和 ...
- EF 管理数据库架构
本章会主要了解EF提供的独立迁移项目,用独立迁移项目自动创建dgml设计关系图和sql脚本. 迁移项目通常也叫(CodeFirst代码优先),在EF中迁移项目是在,在代码中设计数据库,每次对数据库的设 ...
- 洛谷 Luogu P1038 [NOIP2003 提高组] 神经网络
这题看着很吓人实则很简单.求输出层,正着求很麻烦,因为知不道谁连向这个点,所以可以反向建边,反着求. 拓扑+dfs,时间复杂度 \(\text{O(n + m)}\) #include <ios ...