简介

"终结"一般被分为确定性终结(显示清除)与非确定性终结(隐式清除)

  1. 确定性终结主要

    提供给开发人员一个显式清理的方法,比如try-finally,using。
  2. 非确定性终结主要

    提供一个注册的入口,只知道会执行,但不清楚什么时候执行。比如IDisposable,析构函数。

为什么需要终结机制?

首先纠正一个观念,终结机制不等于垃圾回收。它只是代表当某个对象不再需要时,我们顺带要执行一些操作。更加像是附加了一种event事件。

所以网络上有一种说法,IDisposable是为了释放内存。这个观念并不准确。应该形容为一种兜底更为贴切。

如果是一个完全使用托管代码的场景,整个对象图由GC管理,那确实不需要。在托管环境中,终结机制主要用于处理对象所持有的,不被GC和runtime管理的资源。

比如HttpClient,如果没有终结机制,那么当对象被释放时,GC并不知道该对象持有了非托管资源(句柄),导致底层了socket连接永远不会被释放。

如前所述,终结器不一定非得跟非托管资源相关。它的本质是”对象不可到达后的do something“.

比如你想收集对象的创建与删除,可以将记录代码写在构造函数与终结器中

终结机制的源码

源码
namespace Example_12_1_3
{
internal class Program
{
static void Main(string[] args)
{
TestFinalize(); Console.WriteLine("GC is start. ");
GC.Collect();
Console.WriteLine("GC is end. ");
Debugger.Break(); Console.ReadLine();
Console.WriteLine("GC2 is start. ");
GC.Collect();
Console.WriteLine("GC2 is end. ");
Debugger.Break();
Console.ReadLine(); }
static void TestFinalize()
{
var list = new List<Person>(1000);
for (int i = 0; i < 1000; i++)
{
list.Add(new Person());
} var personNoFinalize = new Person2();
Console.WriteLine("person/personNoFinalize分配完成"); Debugger.Break();
}
}
public class Person
{
~Person()
{
Console.WriteLine("this is finalize");
Thread.Sleep(1000);
}
}
public class Person2
{ }
}
IL
	// Methods
.method family hidebysig virtual
instance void Finalize () cil managed
{
.override method instance void [mscorlib]System.Object::Finalize()
// Method begins at RVA 0x2090
// Header size: 12
// Code size: 30 (0x1e)
.maxstack 1 IL_0000: nop
.try
{
// {
IL_0001: nop
// Console.WriteLine("this is finalize");
IL_0002: ldstr "this is finalize"
IL_0007: call void [mscorlib]System.Console::WriteLine(string)
// Console.ReadLine();
IL_000c: nop
IL_000d: call string [mscorlib]System.Console::ReadLine()
IL_0012: pop
// }
IL_0013: leave.s IL_001d
} // end .try
finally
{
// (no C# code)
IL_0015: ldarg.0
IL_0016: call instance void [mscorlib]System.Object::Finalize()
IL_001b: nop
IL_001c: endfinally
} // end handler IL_001d: ret
} // end of method Person::Finalize
汇编
0199097B  nop
0199097C mov ecx,dword ptr ds:[4402430h]
01990982 call System.Console.WriteLine(System.String) (72CB2FA8h)
01990987 nop
01990988 call System.Console.ReadLine() (733BD9C0h)
0199098D mov dword ptr [ebp-40h],eax
01990990 nop
01990991 nop
01990992 mov dword ptr [ebp-20h],offset Example_12_1_3.Person.Finalize()+045h (00h)
01990999 mov dword ptr [ebp-1Ch],0FCh
019909A0 push offset Example_12_1_3.Person.Finalize()+06Ch (019909BCh)
019909A5 jmp Example_12_1_3.Person.Finalize()+057h (019909A7h)

可以看到,C#的析构函数只是一种语法糖。IL重写了System.Object.Finalize方法。在底层的汇编中,直接调用的就是Finalize()

终结的流程

眼见为实

使用windbg看一下底层。

  1. 创建Person对象,是否自动进入finalize queue?

可以看到,当new obj 时,finalize queue中已经有了Person对象的析构函数

  1. GC开始后,是否移动到F-Reachable queue?

可以看到代码中创建的1000个Person的析构函数已经进入了F-Reachable queue

sosex !finq/!frq 指令同样可以输出

  1. 析构对象是否被"复活"?

    GC发生前,在TestFinalize方法中创建了两个变量,person=0x02a724c0,personNoFinalize=0x02a724cc。





    可以看到所属代都为0,且托管堆中都能找到它们。

GC发生后







可以看到,Person2对象因为被回收而在托管堆中找不到了,Person对象因为还未执行析构函数,所以还存在gcroot 。因此并未被回收,且内存代从0代提升到1代

  1. 终结线程是否执行,是否被移出F-Reachable queue





    在GC将托管线程从挂起到恢复正常后,且F-Reachable queue 有值时,终结线程将乱序执行。

    并将它们移出队列

  2. 析构函数的对象是否在第二次GC中释放?

    等到第二次GC发生后,由于对象析构函数已经被执行,不再拥有gcroot,所以托管堆最终释放了该对象,

  3. 析构函数如果没有及时执行完成,又触发了一次GC。会不会再次升代



    答案是肯定的

终结的开销

  1. 如果一个类型具有终结器,将使用慢速分支执行分配操作

    且在分配时还需要额外进入finalize queue而引入的额外开销
  2. 终结器对象至少要经历2次GC才能够被真正释放

    至少两次,可能更多。终结线程不一定能在两次GC之间处理完所有析构函数。此时对象从1代升级到2代,2代对象触发GC的频率更低。导致对象不能及时被释放(析构函数已经执行完毕,但是对象本身等了很久才被释放)。
  3. 对象升代/降代时,finalize queue也要重复调整

    与GC分代一样,也分为3个代和LOH。当一个对象在GC代中移动时,对象地址也需要也需要在finalization queue移动到对应的代中.

    由于finalize queue与f-reachable queue 底层由同一个数组管理,且元素之间并没有留空。所以升代/降代时,与GC代不同,GC代可以见缝插针的安置对象,而finalize则是在对应的代末尾插入,并将后面所有对象右移一个位置

眼见为实

点击查看代码
    public class BenchmarkTester
{
[Benchmark]
public void ConsumeNonFinalizeClass()
{
for (int i = 0; i < 1000; i++)
{
var obj = new NonFinalizeClass();
obj.Age = i; }
}
[Benchmark]
public void ConsumeFinalizeClass()
{
for (int i = 0; i < 1000; i++)
{
var obj = new FinalizeClass();
obj.Age = i; }
}
}

非常明显的差距,无需解释。

总结

一张图带你了解.NET终结(Finalize)流程的更多相关文章

  1. 8张图带你理解Java整个只是网络(转载)

    8张图带你理解Java整个只是网络 一图胜千言,下面图解均来自Program Creek 网站的Java教程,目前它们拥有最多的票选.如果图解没有阐明问题,那么你可以借助它的标题来一窥究竟. 1.字符 ...

  2. 47 张图带你 MySQL 进阶!!!

    我们在 MySQL 入门篇主要介绍了基本的 SQL 命令.数据类型和函数,在局部以上知识后,你就可以进行 MySQL 的开发工作了,但是如果要成为一个合格的开发人员,你还要具备一些更高级的技能,下面我 ...

  3. 5000字 | 24张图带你彻底理解Java中的21种锁

    本篇主要内容如下: 本篇文章已收纳到我的Java在线文档. Github 我的SpringCloud实战项目持续更新中 帮你总结好的锁: 序号 锁名称 应用 1 乐观锁 CAS 2 悲观锁 synch ...

  4. 炸裂!MySQL 82 张图带你飞

    之前两篇文章带你了解了 MySQL 的基础语法和 MySQL 的进阶内容,那么这篇文章我们来了解一下 MySQL 中的高级内容. 其他文章: 138 张图带你 MySQL 入门 47 张图带你 MyS ...

  5. 35 张图带你 MySQL 调优

    这是 MySQL 基础系列的第四篇文章,之前的三篇文章见如下链接 138 张图带你 MySQL 入门 47 张图带你 MySQL 进阶!!! 炸裂!MySQL 82 张图带你飞 一般传统互联网公司很少 ...

  6. 产品经理-需求分析-用户故事-敏捷开发 详解 一张图帮你了解Scrum敏捷流程

    产品经理-需求分析-用户故事-敏捷开发 详解 用户故事是从用户的角度来描述用户渴望得到的功能.一个好的用户故事包括三个要素:1. 角色:谁要使用这个功能.2. 活动:需要完成什么样的功能.3. 商业价 ...

  7. 终结 finalize()和垃圾回收(garbage collection)

    1.为什么要有finalize()方法? 假定你的对象(并非使用new)获得了一块"特殊"的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以他不知道该如何释放该对象 ...

  8. 10张图带你深入理解Docker容器和镜像

    http://dockone.io/article/783 [编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. Doc ...

  9. 四张图带你了解Tomcat系统架构

    一.Tomcat顶层架构 先上一张Tomcat的顶层结构图(图A),如下: Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service ...

  10. 一张图带你了解-常见面试之JUC包详解

    面试时经常问到JUC包下的类及特性,现在用一张图总结下

随机推荐

  1. 【Maxwell】01 安装及入门

    官网地址: https://maxwells-daemon.io/ 下载地址(版本发行): https://github.com/zendesk/maxwell/releases 参考教程自尚硅谷视频 ...

  2. 【Vue】01 基础语法

    Hello Vue的演示案例: <!DOCTYPE html> <html lang="en" xmlns:v-bind="http://www.w3. ...

  3. 关于工业AI辅助制造(模具设计、模样生产制造环节)

    关于工业AI辅助制造(模具设计.模样生产制造环节) AI技术的具体使用场景: AI辅助模具设计: AI辅助模具安装工艺参数调整. 具体方案设想: AI辅助模具设计: 使用AI大模型对历史已有的设计方案 ...

  4. 大语言模型可以自动生成sql语句吗?

    大语言模型的能力已经是毋庸置疑的了,随着ChatGPT的霸榜,各种语言模型的应用也多了起来,这时候突然有一个意外,那就是:大语言模型可以自动生成sql语句吗? 之所以有这个疑问,主要是因为sql正好是 ...

  5. 最小二乘法的矩阵正则化改进——“岭回归”和“LASSO回归”算法

    看代码过程中发现了一个很奇怪的概念,叫做"最小二乘法的矩阵正则化",这个词汇十分的陌生,虽然最小二乘法是知道的,但是用了矩阵正则化的最小二乘法是个什么东西呢? 相关代码见: 强化学 ...

  6. Canvas简历编辑器-图形绘制与状态管理(轻量级DOM)

    Canvas简历编辑器-图形绘制与状态管理(轻量级DOM) 在前边我们聊了数据结构的设计和剪贴板的数据操作,那么这些操作都还是比较倾向于数据相关的操作,那么我们现在就来聊聊基本的图形绘制以及图形状态管 ...

  7. .Net Aspire次体验

    上次用上了在微软MVP的带领下用上了Aspire,在开发阶段隐藏了细节,什么都不用做,点个调试按钮就跑起来了,可是部署时出现了难题, 因为发布时只能选择Azure环境,为此注册了Azure,开了科网. ...

  8. 与LLMs进行在IDE中直接、无需提示的交互是工具构建者探索的一个有希望的未来方向

    这个观点在卡内基梅隆大学与谷歌研究人员合作文章 <Using an LLM to Help With Code Understanding> 中提出. 论文地址:https://dl.ac ...

  9. 别再被坑了! JavaScript类型检测的最佳实践

    别再被坑了! JavaScript类型检测的最佳实践 在 JavaScript 中,我们经常需要判断一个变量的类型.这个需求在编程中非常常见,因为不同类型的数据会影响到我们的代码逻辑. JavaScr ...

  10. 使用image-syncer镜像同步工具将阿里云镜像仓库镜像迁移至私有Harbor

    借助于阿里云开源的镜像同步工具image-syncer实现harbor及阿里云镜像仓库之间的镜像迁移 下载镜像同步工具 curl -fL "https://wiseo-generic.pkg ...