.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 ...
随机推荐
- IDEA连接数据库
我只想卷死各位,或者被各位卷死 在入门案例映射配置文件中存在报红的情况.问题如下: 产生的原因:Idea和数据库没有建立连接,不识别表信息.但是大家一定要记住,它并不影响程序的执行. 解决方式:在Id ...
- gowWeb之错误处理和返回响应
Go Web开发进阶实战(gin框架) 讲师:李文周老师 https://study.163.com/course/introduction.htm?courseId=1210171207&t ...
- 【小小Demo】在线聊天小🌰子
easy-chat 一个简单的即时通讯demo. 环境 jdk1.8 idea maven springboot 2.1.1.RELEASE websocket 项目启动 修改maven设置 打开ID ...
- 【线上技术分享】即构&MobTech袤博移动游戏开发者全能进阶沙龙
游戏行业的兴起与当前移动互联网用户碎片化.休闲化的生活特征密不可分,在用户旺盛的需求下,游戏行业迎来了绝佳的发展机遇,今年上半年已多款游戏DAU过亿. 市场的火爆也为游戏行业带来了异常激烈的竞争,加上 ...
- 抽象语法树AST必知必会
1 介绍 AST 打开前端项目中的 package.json,会发现众多工具已经占据了我们开发日常的各个角落,例如 JavaScript 转译.CSS 预处理.代码压缩.ESLint.Prettier ...
- 你不知道的 HTTP Referer
前言 上周突然发现自己的自己站点的图片全都403了,之前还是好好的,图片咋就全都访问不了呢?由于我每次发文章都是先发了掘金,然后再从掘金拷贝到我自己的站点,这样我就不用在自己的站点去上传图片了,非常方 ...
- 写一段python下载商品图片的代码
以下是一个简单的Python代码示例,用于下载商品图片: import requests import os def download_image(url, save_path): response ...
- 重返照片的原始世界:我为.NET打造的RAW照片解析利器
重返照片的原始世界:我为.NET打造的RAW照片解析利器 如果你是我的老读者,你可能还记得,在2019年,我冒险进入了一片神秘的领域--用C#解析RAW格式的照片: 20191208 - 用.NET解 ...
- (转)[Android测试] Android Studio+Appium+Java+Windows 自动化测试之二:Appium环境安装搭建
一.需要下载安装的东西 1. 文件下载 网上也有挺多安装教程的,这里我提供我的安装方法.Win10 64位.一些文件我在后面打包. 2016.9.12号本人安装记录. SDK: 由于我是搞Androi ...
- [go]封装go的docker镜像
前言 多阶段封装docker镜像,使用scratch镜像,尽量减小镜像包的体积. 封装用于编译的go镜像 Dockerfile FROM golang:1.20.1 AS builder WORKDI ...