一张图带你了解.NET终结(Finalize)流程
简介
"终结"一般被分为确定性终结(显示清除)与非确定性终结(隐式清除)
- 确定性终结主要
提供给开发人员一个显式清理的方法,比如try-finally,using。 - 非确定性终结主要
提供一个注册的入口,只知道会执行,但不清楚什么时候执行。比如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看一下底层。
- 创建Person对象,是否自动进入finalize queue?
可以看到,当new obj 时,finalize queue中已经有了Person对象的析构函数
- GC开始后,是否移动到F-Reachable queue?
可以看到代码中创建的1000个Person的析构函数已经进入了F-Reachable queue
sosex !finq/!frq 指令同样可以输出
- 析构对象是否被"复活"?
GC发生前,在TestFinalize方法中创建了两个变量,person=0x02a724c0,personNoFinalize=0x02a724cc。
可以看到所属代都为0,且托管堆中都能找到它们。
GC发生后
可以看到,Person2对象因为被回收而在托管堆中找不到了,Person对象因为还未执行析构函数,所以还存在gcroot 。因此并未被回收,且内存代从0代提升到1代
终结线程是否执行,是否被移出F-Reachable queue
在GC将托管线程从挂起到恢复正常后,且F-Reachable queue 有值时,终结线程将乱序执行。
并将它们移出队列
析构函数的对象是否在第二次GC中释放?
等到第二次GC发生后,由于对象析构函数已经被执行,不再拥有gcroot,所以托管堆最终释放了该对象,
析构函数如果没有及时执行完成,又触发了一次GC。会不会再次升代
答案是肯定的
终结的开销
- 如果一个类型具有终结器,将使用慢速分支执行分配操作
且在分配时还需要额外进入finalize queue而引入的额外开销 - 终结器对象至少要经历2次GC才能够被真正释放
至少两次,可能更多。终结线程不一定能在两次GC之间处理完所有析构函数。此时对象从1代升级到2代,2代对象触发GC的频率更低。导致对象不能及时被释放(析构函数已经执行完毕,但是对象本身等了很久才被释放)。 - 对象升代/降代时,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)流程的更多相关文章
- 8张图带你理解Java整个只是网络(转载)
8张图带你理解Java整个只是网络 一图胜千言,下面图解均来自Program Creek 网站的Java教程,目前它们拥有最多的票选.如果图解没有阐明问题,那么你可以借助它的标题来一窥究竟. 1.字符 ...
- 47 张图带你 MySQL 进阶!!!
我们在 MySQL 入门篇主要介绍了基本的 SQL 命令.数据类型和函数,在局部以上知识后,你就可以进行 MySQL 的开发工作了,但是如果要成为一个合格的开发人员,你还要具备一些更高级的技能,下面我 ...
- 5000字 | 24张图带你彻底理解Java中的21种锁
本篇主要内容如下: 本篇文章已收纳到我的Java在线文档. Github 我的SpringCloud实战项目持续更新中 帮你总结好的锁: 序号 锁名称 应用 1 乐观锁 CAS 2 悲观锁 synch ...
- 炸裂!MySQL 82 张图带你飞
之前两篇文章带你了解了 MySQL 的基础语法和 MySQL 的进阶内容,那么这篇文章我们来了解一下 MySQL 中的高级内容. 其他文章: 138 张图带你 MySQL 入门 47 张图带你 MyS ...
- 35 张图带你 MySQL 调优
这是 MySQL 基础系列的第四篇文章,之前的三篇文章见如下链接 138 张图带你 MySQL 入门 47 张图带你 MySQL 进阶!!! 炸裂!MySQL 82 张图带你飞 一般传统互联网公司很少 ...
- 产品经理-需求分析-用户故事-敏捷开发 详解 一张图帮你了解Scrum敏捷流程
产品经理-需求分析-用户故事-敏捷开发 详解 用户故事是从用户的角度来描述用户渴望得到的功能.一个好的用户故事包括三个要素:1. 角色:谁要使用这个功能.2. 活动:需要完成什么样的功能.3. 商业价 ...
- 终结 finalize()和垃圾回收(garbage collection)
1.为什么要有finalize()方法? 假定你的对象(并非使用new)获得了一块"特殊"的内存区域,由于垃圾回收器只知道释放那些经由new分配的内存,所以他不知道该如何释放该对象 ...
- 10张图带你深入理解Docker容器和镜像
http://dockone.io/article/783 [编者的话]本文用图文并茂的方式介绍了容器.镜像的区别和Docker每个命令后面的技术细节,能够很好的帮助读者深入理解Docker. Doc ...
- 四张图带你了解Tomcat系统架构
一.Tomcat顶层架构 先上一张Tomcat的顶层结构图(图A),如下: Tomcat中最顶层的容器是Server,代表着整个服务器,从上图中可以看出,一个Server可以包含至少一个Service ...
- 一张图带你了解-常见面试之JUC包详解
面试时经常问到JUC包下的类及特性,现在用一张图总结下
随机推荐
- 【Git】Gitee 码云的使用
1.注册.登陆.设置配置 以上步骤省略,不需要太多指示操作 2.配置SSH公钥: 先进入自己的用户目录下面 C:\Users\Administrator\ 然后右键空白位置[Git Bash Here ...
- 超简单stable_diffusion + novelai一键部署教程
视频教程地址: 超简单stable_diffusion + novelai一键部署教程 个人的启动命令: sudo docker run -it --rm -e NVIDIA_DISABLE_REQU ...
- 台式机电脑散热之狂想曲——主机机箱散热的另类方法(纯空想中ing)
本博文是一篇狂想曲,之所以叫狂想曲是因为本文只是博主在无聊时突发奇想,而且仅停留于想的阶段,所以本文内容不用太过认真. 事情是这样的,博主有一台式机,有事没事的就喜欢宅在宿舍里面,有时候还能偶然用这台 ...
- 中国超级计算机为什么不能为AI提供算力?
网上看到这样的帖子: https://www.zhihu.com/question/609008408/answer/3130831897 ============================== ...
- WPF Boolean类型转化器收集 反转转化器
参考链接 https://stackoverflow.com/questions/534575/how-do-i-invert-booleantovisibilityconverter Boolean ...
- vue之es6语法
1.背景 2.let与var与const的区别 <!DOCTYPE html> <html lang="en"> <head> <meta ...
- VUE——语法糖
- Python 开发环境的准备以及一些常用类库模块的安装
在学习和开发Python的时候,第一步的工作就是先准备好开发环境,包括相关常用的插件,以及一些辅助工具,这样我们在后续的开发工作中,才能做到事半功倍.下面介绍一些Python 开发环境的准备以及一些常 ...
- Mac 使用 Caps Lock 键切换输入法失灵问题解决
Mac 上的 Caps Lock 键对于多语言用户来说,除了切换输入大小写的作用外还承担着切换输入法的功能.正常情况下,轻按一下 Caps Lock 键是切换输入法,长按是切换输入大小写.然而有时这个 ...
- Kubernetes-3:使用kubeadm部署k8s环境及常见报错解决方法
k8s集群安装 环境说明: k8s-Master-Centos8 ip:192.168.152.53 k8s-Node1-Centos7 ip:192.168.152.253 k8s-Node2-Ce ...