首页 > 其他分享 >一张图带你了解.NET终结(Finalize)流程

一张图带你了解.NET终结(Finalize)流程

时间:2024-10-11 15:44:02浏览次数:9  
标签:Console Finalize 对象 终结 queue GC IL NET 图带

简介

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

  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()

终结的流程

image

眼见为实

使用windbg看一下底层。

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

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

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

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

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

  1. 析构对象是否被"复活"?
    GC发生前,在TestFinalize方法中创建了两个变量,person=0x02a724c0,personNoFinalize=0x02a724cc。
    image
    image
    可以看到所属代都为0,且托管堆中都能找到它们。

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

  1. 终结线程是否执行,是否被移出F-Reachable queue
    image
    image
    在GC将托管线程从挂起到恢复正常后,且F-Reachable queue 有值时,终结线程将乱序执行。
    并将它们移出队列
    image

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

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

终结的开销

  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;

            }
        }
    }

image

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

总结

标签:Console,Finalize,对象,终结,queue,GC,IL,NET,图带
From: https://www.cnblogs.com/lmy5215006/p/18456380

相关文章

  • Pixnet台湾最火社交网站
    Pixnet是一个台湾的网络社群服务平台。它由曾皇霖与刘昊恩在2003年创立,目前由城邦媒体控股集团的子公司优像数位媒体经营。该网站主要提供以下服务:1.**部落格(博客)**:用户可以在上面发表个人的文字内容、观点、经历分享等,类似于个人的网络日志空间,用户可以对文章进行编辑、排版等......
  • EfficientNet
    资料:细品EfficientNet-知乎(zhihu.com)【论文解读】一文看懂EfficientnetB0~B7模型所有细节-知乎(zhihu.com)mmlab之调用mmpretrain预训练模型_mmpretrain下游-CSDN博客在mmcv中使用EfficientNet的config:1backbone=dict(2type='mmdet.EfficientNe......
  • crit: Microsoft.AspNetCore.Server.Kestrel[0] Unable to start Kestrel. Interop+Cr
    域名证书没有放在指定的位置错误信息crit:Microsoft.AspNetCore.Server.Kestrel[0]UnabletostartKestrel.Interop+Crypto+OpenSslCryptographicException:error:2006D080:BIOroutines:BIO_new_file:nosuchfileatInterop.Crypto.CheckValidOpenSslHandle(Saf......
  • visual studio 2022 安装net旧版本(net framework4.0和4.5)
    原文链接:visualstudio2022安装net旧版本(netframework4.0和4.5)_vs20224.0框架问题:VisualStudio2022安装程序中单个组件中没有.netframework4.0或者.netframework4.5的问题,NETSDK下载 Download.NETSDKsforVisualStudio下载没有4.5和4.0的开发包其他Net版本直......
  • YoloDotNet v2.1:实时物体检测的利器oX
    项目介绍YoloDotNetv2.1是一个基于C#和.NET8的实时物体检测框架,专为图像和视频中的物体检测而设计。它集成了Yolov8~Yolov11模型,通过ML.NET和ONNX运行时实现高效的物体检测,并支持GPU加速(使用CUDA)。YoloDotNet不仅支持传统的物体检测,还涵盖了分类、OBB检测、......
  • telnet操作中兴设备
    importtelnetlib,re,os,threading,multiprocessing,datetimeimportpandasaspdfrompandaspd.set_option('display.width',None)pd.options.display.max_columns=Nonepd.options.display.max_rows=Nonepath=os.getcwd()deftelnet(host,port......
  • Kubernetes(K8s)技术深度解析与实践案例
    Kubernetes(K8s)技术深度解析与实践案例Kubernetes(简称K8s)是一个开源的容器编排系统,用于自动化应用程序的部署、扩展和管理。自2014年首次亮相以来,K8s迅速成为容器编排领域的行业标准,其设计哲学、可扩展性和强大的社区支持是其成功的关键因素。本文将深入探讨K8s的核心概念、......
  • The Network Program Log One(网络自动编程)
    ​Practice1importparamikoimporttimefromncclientimportmanagerfromncclient.xml_importto_elefrompysnmp.hlapiimport*importsocketclassDevice:definit(self,ip,name):self.ip=ipself.name=nameself.vty=Noneself.ssh_session=Nonedef......
  • The Network Program Log Three(代码调试过程3)
    ​importpysharkimportos没有提示files=os.listdir('./shark_files/')forfileinfiles:print(file)ps=pyshark.FileCapture('./shark_files/'+file,tshark_path='D:/ProgramFiles/Wireshark/Wireshark.exe')forpktinps:prin......
  • The Network Program Log Two (Scapy)
    ​【Scapy】【使用scapy处理数据包】scapy的使用fromscapy.allimport*fromscapy.layers.inetimport*pac=dir(scapy.layers)print(pac) #执行代码后,会输出Scapy中的各层['builtins','cached','doc','file','loader','name&......