简介

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

  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. blender建模渲染Tips

    blender渲染 灯光的三种方式 1,常规灯光:shift+A选择灯光. 2,世界环境光:右侧地球图标调整. 3,物体自发光:把渲染物体变成一个发光体来进行调节灯光. 渲染视窗的调节 ctrl+b裁 ...

  2. 【转载】 Parallel Computing in Python using mpi4py

    原地址: https://research.computing.yale.edu/sites/default/files/files/mpi4py.pdf ====================== ...

  3. 【转载】 numpy数据类型dtype转换

    原文地址: https://www.cnblogs.com/hhh5460/p/5129032.html =============================================== ...

  4. tf.py_func的一些使用笔记——TensorFlow1.x

    tensorflow.py_func是TensorFlow1.x版本下的函数,在TensorFlow.2.x已经不建议使用了,但是依然可以通过tf.compat.v1.py_func的方式来进行调用. ...

  5. 【VMware ESXi】把硬盘当内存用?VMware 内存分层(Memory Tiering),你值得拥有!

    VMware vSphere 8.0 U3 发布了一个非常有意义的功能叫内存分层(Memory Tiering),以利用基于 PCIe 的 NVMe 设备充当第二层(辅助)内存,从而使 ESXi 主机 ...

  6. 使用Web Component定义自己的专属网页组件

    什么是Web Component Web Component是一套Web浏览器的技术和规范,能够让开发者定制自己的HTML元素 来自MDN的描述: Web Component 是一套不同的技术,允许你 ...

  7. 22张图详解浏览器请求数据包如何到达web服务器(搞懂网络可以毕业了)

    浏览器的请求数据包如何到达web服务器? 很多读者对于其中的完整流程不是特别的了解,下面一口君通过这22张图,详细的讲解我们点击浏览器的网址之后,数据包是如何经过重重险阻到达web server的. ...

  8. flink + iceberg 快速搭建指南

    flink + iceberg 快速搭建 the environment includes: minio iceberg flink Centos 更换 tencent 的yum源 备份系统旧配置文件 ...

  9. 使用sl+tmux哇娃

    0x01 背景 2岁多的小娃不肯刷牙,有时看故事书时会配合刷一会儿,但偶尔也不好使.突然想到TA,之前在电脑桌旁边捣乱时,给ta看过console中的小火车,ubuntu中安装sl命令后就可以看到,用 ...

  10. Android 存储概览

    存储区​ Android 一开始就将存储区分为内部存储和外部存储,对应手机自带的存储和可插拔的 sd 卡(可类比于 PC 的硬盘和 U盘). 内部存储容量有限,Google 建议 App 数据尽量存储 ...