从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制

如果你经常看开源项目的源码,你会发现很多Dispose方法中都有这么一句代码: GC.SuppressFinalize(this); ,看过一两次可能无所谓,看多了就来了兴趣,这篇就跟大家聊一聊。

一:背景

1. 在哪发现的

相信现在Mysql在.Net领域中铺的面越来越广了,C#对接MySql的MySql.Data类库的代码大家可以研究研究,几乎所有操作数据库的几大对象:MySqlConnection,MySqlCommand,MySqlDataReader以及内部的Driver都存在 GC.SuppressFinalize(this)代码。


public sealed class MySqlConnection : DbConnection, ICloneable
{public new void Dispose(){Dispose(disposing: true);GC.SuppressFinalize(this);}
}public sealed class MySqlCommand : DbCommand, IDisposable, ICloneable
{public new void Dispose(){Dispose(disposing: true);GC.SuppressFinalize(this);}
}

2. GC.SuppressFinalize 场景在哪里

先看一下官方对这个方法的解释,如下所示:

        //// Summary://     Requests that the common language runtime not call the finalizer for the specified//     object.//// Parameters://   obj://     The object whose finalizer must not be executed.//// Exceptions://   T:System.ArgumentNullException://     obj is null.[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)][SecuritySafeCritical]public static void SuppressFinalize(object obj);

意思就是说: 请求 CLR 不要调用指定对象的终结器,如果你对终结器的前置基础知识不足,那这句话肯定不是很明白,既然都执行了Dispose,说明非托管资源都被释放了,怎么还压制CLR不要调用Finalize呢?删掉和不删掉这句代码有没有什么严重的后果,GC类的方法谁也不敢动哈。。。为了彻底讲清楚,有必要说一下Finalize整个原理。

二:资源管理

我们都知道C#是一门托管语言,它的好处就是不需要程序员去关心内存的分配和释放,由CLR统一管理,这样编程门槛大大降低,天下攘攘皆为利来,速成系的程序员就越来越多~

1. 对托管资源和非托管资源理解

<1> 托管资源

这个很好理解,你在C#中使用的值类型,引用类型都是统一受CLR分配和GC清理。

<2> 非托管资源

在实际业务开发中,我们的代码不可能不与外界资源打交道,比如说文件系统,外部网站,数据库等等,就拿写入文件的StreamWriter举例,如下代码:

        public static void Main(string[] args){StreamWriter sw = new StreamWriter("xxx.txt");sw.WriteLine("....");}

为什么能够写入文件?那是因为我们的代码是请求windows底层的Win32 Api帮忙写入的,这就有意思了,因为这个场景有第三者介入,sw是引用类型受CLR管理,win32 api属于外部资源和.Net一点关系都没有,如果你在用完sw之后没有调用close方法的话,当某个时候GC回收了托管堆上的sw后,这给被打开的win32 api文件句柄再也没有人可以释放了,资源就泄露了,如果没看懂,我画张图:

三:头疼的非托管资源解决方案

1. 使用析构函数

很多时候程序员就是在使用完类之后因为种种原因忘记了手动执行Close方法造成了资源泄露,那有没有一种机制可以在GC回收堆对象的时候回调我的一个自定义方法呢?如果能实现就????????了,这样我就可以在自定义方法中做全局的控制。

其实这个自定义方法就是析构函数,接下来我把上面的 StreamWriter 改造下,将 Close() 方法放置在析构函数中,先看一下代码:

public class Program{public static void Main(string[] args){MyStreamWriter sw = new MyStreamWriter("xxx.txt");sw.WriteLine("....");GC.Collect();Console.ReadLine();}}public class MyStreamWriter : StreamWriter{public MyStreamWriter(string filename) : base(filename) { }~MyStreamWriter(){Console.WriteLine("嘿嘿,忘记调用Close方法了吧!我来帮你");base.Dispose(false);Console.WriteLine("非托管资源已经帮你释放啦,不要操心了哈");}}--------- output -----------嘿嘿,忘记调用Close方法了吧!我来帮你
非托管资源已经帮你释放啦,不要操心了哈

四:析构函数被执行的底层分析

让GC来通知我的回调方法这本身就很????????,但仔细想想,在垃圾回收时,CLR不是将所有线程都挂起了吗?怎么还有活动的线程,而且这个线程是来自哪里?线程池吗?好,先从理论跟和大家分析一下,析构函数在CLR层面称为Finalize方法,为了方便后面通过windbg去验证,这里统一都叫Finalize方法,提前告知。

1. 原理步骤

<1> CLR在启动时会构建一个“Finalize全局数组”和“待处理Finalize数组” ,所有定义Finalize方法的类,它的引用地址全部额外再灌到“Finalize全局数组”中。

<2> CLR启动一个专门的“Finalize线程”让其全权监视“待处理Finalize数组”。

<3> GC在开启清理前标记对象引用时,如发现某一个对象只有一个在Finalize数组中的引用,说明此对象是垃圾了,CLR将该对象地址转移到另外一个 “待处理Finalize” 数组中。

<4> 由于该对象还存在引用,所以GC放了一马,然后“Finalize线程”监视到了 “待处理Finalize数组” 新增的对象,取出该对象并执行该对象的Finalize方法。

<5> 由于是破坏性取出,此时该对象再无任何引用,下次GC启动时就会清理出去。

看文字有点绕,我画一张图帮大家理解下。

2. windbg验证

<1> 修改Main代码如下,抓一下dump文件看看 MyStreamWriter是否在Finalize全局数组中。

public static void Main(string[] args){MyStreamWriter sw = new MyStreamWriter("xxx.txt");sw.WriteLine("....");Console.ReadLine();}``` C#0:000> !FinalizeQueue
SyncBlocks to be cleaned up: 0
Free-Threaded Interfaces to be released: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 13 finalizable objects (0000018c2a9b7a80->0000018c2a9b7ae8)generation 1 has 0 finalizable objects (0000018c2a9b7a80->0000018c2a9b7a80)generation 2 has 0 finalizable objects (0000018c2a9b7a80->0000018c2a9b7a80)Ready for finalization 0 objects (0000018c2a9b7ae8->0000018c2a9b7ae8)Statistics for all finalizable objects (including all objects ready for finalization):MT    Count    TotalSize Class Name00007ff8e7afb2a8        1           32 System.Runtime.InteropServices.NativeBuffer+EmptySafeHandle00007ff8e7a94078        1           32 Microsoft.Win32.SafeHandles.SafePEFileHandle00007ff8e7a843b0        1           32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle00007ff8e7a84320        1           32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle00007ff8e7b001b8        1           40 System.Runtime.InteropServices.SafeHeapHandleCache00007ff8e7ad6df0        1           40 System.Runtime.InteropServices.SafeHeapHandle00007ff8e7b133d0        2           64 Microsoft.Win32.SafeHandles.SafeRegistryHandle00007ff8e7a995d0        2           64 Microsoft.Win32.SafeHandles.SafeFileHandle00007ff8e7a93b48        1           64 System.Threading.ReaderWriterLock00007ff8e7b14d38        1          104 System.IO.FileStream00007ff889d45b18        1          112 ConsoleApp2.MyStreamWriterTotal 13 objects

很惊喜的看到 MyStreamWriter 就在其中,符合图中所示。

<2> 查看是否有专门的 “Finalize线程” ,可以通过 !threads 命令查看。


0:000> !threads
ThreadCount:      2
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       0
Hosted Runtime:   noLock  ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception0    1  bf4 0000018c2a990f00    2a020 Preemptive  0000018C2C429168:0000018C2C429FD0 0000018c2a965220 1     MTA6    2 44f4 0000018c2a9b9450    2b220 Preemptive  0000000000000000:0000000000000000 0000018c2a965220 0     MTA (Finalizer)

看到没,线程2标记了 MTA (Finalizer), 说明果然有执行Finalizer方法的专有线程。????????????

<3> 由于水平有限,不知道怎么去看 “待处理Finalize数组”,所以只能验证等GC回收之后,看下 “Finalize全局数组”中是否还存在MyStreamWriter即可。

public static void Main(string[] args){MyStreamWriter sw = new MyStreamWriter("xxx.txt");sw.WriteLine("....");GC.Collect();Console.ReadLine();}------- output ---------嘿嘿,忘记调用Close方法了吧! 我来帮你
非托管资源已经帮你释放啦,不要操心了哈0:000> !FinalizeQueue
SyncBlocks to be cleaned up: 0
Free-Threaded Interfaces to be released: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 5 finalizable objects (0000021e8051a798->0000021e8051a7c0)generation 1 has 5 finalizable objects (0000021e8051a770->0000021e8051a798)generation 2 has 0 finalizable objects (0000021e8051a770->0000021e8051a770)Ready for finalization 0 objects (0000021e8051a7c0->0000021e8051a7c0)Statistics for all finalizable objects (including all objects ready for finalization):MT    Count    TotalSize Class Name00007ff8e7afb2a8        1           32 System.Runtime.InteropServices.NativeBuffer+EmptySafeHandle00007ff8e7a94078        1           32 Microsoft.Win32.SafeHandles.SafePEFileHandle00007ff8e7a843b0        1           32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle00007ff8e7a84320        1           32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle00007ff8e7b001b8        1           40 System.Runtime.InteropServices.SafeHeapHandleCache00007ff8e7ad6df0        1           40 System.Runtime.InteropServices.SafeHeapHandle00007ff8e7a995d0        2           64 Microsoft.Win32.SafeHandles.SafeFileHandle00007ff8e7a93b48        1           64 System.Threading.ReaderWriterLock00007ff8e7a96a10        1           96 System.Threading.ThreadTotal 10 objects

可以看到这时候 “全局数组” 没有引用了,再看一下托管堆是否还存在 MyStreamWriter以及线程栈中是否还有对象引用地址。


0:000> !dumpheap Address               MT     Size
00007ff889d25b00        1          112 ConsoleApp2.MyStreamWriterTotal 423 objects0:000> !clrstack -l
OS Thread Id: 0x1b00 (0)Child SP               IP Call Site
0000007ecdffe9e0 00007ff8e88c20cc System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)LOCALS:<no data><no data><no data><no data><no data><no data>
0000007ecdffea70 00007ff8e88c1fd5 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)LOCALS:<no data><no data>
0000007ecdffead0 00007ff8e80770f4 System.IO.StreamReader.ReadBuffer()LOCALS:<no data><no data>
0000007ecdffeb20 00007ff8e8077593 System.IO.StreamReader.ReadLine()LOCALS:<no data><no data><no data><no data>
0000007ecdffeb80 00007ff8e8a68b0d System.IO.TextReader+SyncTextReader.ReadLine()
0000007ecdffebe0 00007ff8e8860d98 System.Console.ReadLine()
0000007ecdffec10 00007ff889e30959 ConsoleApp2.Program.Main(System.String[])
0000007ecdffeea8 00007ff8e9396c93 [GCFrame: 0000007ecdffeea8]

可以看到MyStreamWriter还是存在于托管堆,但是线程栈已再无它的引用地址,就这样告别了全世界,下次GC启动就要被彻底运走了。

五:回头再看 SuppressFinalize

如果你看懂了上面 Finalize 原理,再来看 SuppressFinalize的解释:‘请求 CLR 不要调用指定对象的终结器’。

就是说当你手动调用Dispose或者Close方法释放了非托管资源后,通过此方法强制告诉CLR不要再触发我的析构函数了,否则再执行析构函数相当于又做了一次清理非托管资源的操作,造成未知风险。

好了,本篇就说这么多,希望你对有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NIO工作方式浅析

java Socket 工作机制 Socket是描述计算机之前相互通信的一种抽象功能。通过基于TCP/IP的流套接字协议建立连接A机器B机器通信—建立Socket连接—通过TCP连接&#xff08;端口号指定唯一应用&#xff09;----IP寻址&#xff08;寻找唯一主机&#xff09;----最终找到唯一主机上…

bufferedimage生成的图片模糊_Kaptcha图片验证码工具

阅读文本大概需要3分钟。验证码的作用图片验证码自从诞生以来从未被抛弃&#xff0c;依然发出属于它所应有的光。验证码经常验证如下一些场景。1、用户登录&#xff0c;防止机器人登录2、论坛留言&#xff0c;防止恶意灌水3、短信验证码发送&#xff0c;防止盗刷短信Kaptcha 简…

[Java基础]对象(反)序列化流

对象序列化流: 代码如下: package ObjectOutputStreamPack;import java.io.Serializable;public class Student implements Serializable {private String name;private int age;public Student() {}public Student(String name, int age) {this.name name;this.age age;}pu…

C# 9 新特性:代码生成器、编译时反射

前言今天 .NET 官方博客宣布 C# 9 Source Generators 第一个预览版发布&#xff0c;这是一个用户已经喊了快 5 年特性&#xff0c;今天终于发布了。简介Source Generators 顾名思义代码生成器&#xff0c;它允许开发者在代码编译过程中获取查看用户代码并且生成新的 C# 代码参与…

I/O性能与可靠性

I/O调优 磁盘I/O优化 性能检测&#xff1a; 压力测试应用程序&#xff0c;观察系统I/O wait指标是否正常&#xff0c;例如有n个CPU&#xff0c;利息情况下I/O wait参数不超过25%&#xff0c;如果超过&#xff0c;就是这个程序的瓶颈就是在IO操作上了可以用iostat命令查看另外…

.NET开源工具类库:Masuit.Tools

【开源框架】| 通用工具类库这是恰童鞋骚年的第223篇原创文章本文介绍一个我的同事【懒得勤快】&#xff08;人称勤快哥&#xff0c;我们叫他骚哥&#xff09;写的一个.NET开源工具类库项目&#xff0c;包含一些常用的操作类&#xff0c;大都是静态类&#xff0c;加密解密&…

[Java基础]字节,字符打印流

代码如下&#xff1a; package PrintWriterPack;import java.io.FileNotFoundException; import java.io.PrintWriter;public class PrintWriterDemo {public static void main(String[] args) throws FileNotFoundException {PrintWriter pw new PrintWriter("D:\\Java…

javaI/O包中的包装模式

设计模式解析–适配器模式 对适配器模式功能比较好理解&#xff0c;就是讲一个类的接口换成客户端所能接受的另外一个接口&#xff0c;从而使两国接口不匹配而无法在一起工作的两个类能在一起工作。 适配器的结构 适配器UML图如下 Target&#xff08;目标接口&#xff09;&…

DevOps vs. Agile:它们有什么共同点?

导语DevOps与Agile有很多不同&#xff0c;但它们之间仍可发现很多共同点&#xff0c;这篇文章为读者揭晓。正文DevOps和Agile之间有着明显的关系。Agile是方法论&#xff0c;Scrum是框架&#xff0c;并DevOps随着看板也落在了Agile的“伞”下。精益&#xff0c;大规模的Scrum&a…

[Java基础]Properties

代码如下: package PropertiesPack;import java.util.Properties; import java.util.Set;public class PropertiesDemo01 {public static void main(String[] args){Properties prop new Properties();prop.put("001","Tom");prop.put("002",&…

空心点_空心砖的新玩法,看完大开眼界

我们所见的空心砖大都在建筑工地上常见的有水泥空心砖和粘土空心砖特点是轻质、环保、保温和隔音如此常见的空心砖仅仅是只为建筑而生吗不是&#xff0c;不管是古朴砖红色空心砖&#xff0c;还是高冷的水泥空心砖他们可以演绎不同的角色甚至让每个渴望回归本真的我们&#xff0…

IO与零拷贝

IO与零拷贝 零拷贝基本介绍 零拷贝时网络编程的一个关键优化点在Java程序中&#xff0c;常用的零拷贝又mmap&#xff08;内存映射&#xff09;和sendFile。那么在OS中的设计时如何&#xff0c;我们需分析mmap和sendFile对比最后通过案例分析 用户进程与操作系统关系 我们先…

从堆里找回“丢失”的代码

前言 前一阵子&#xff0c;使用小乌龟&#xff08;TortoiseGit&#xff09;提交代码的时候&#xff0c;错误的 Revert 了部分代码&#xff0c;本文记录了找回这部分代码的过程。文章标题致敬张银奎老师《格蠹汇编》的第一章 —— 从堆里抢救丢失的博客。说明&#xff1a; 本文的…

[Java基础]线程基础与实现多线程

代码如下&#xff1a; package MyThreadPack;public class MyThread extends Thread {Overridepublic void run() {for (int i 0;i<100;i){System.out.println(i);}} }package MyThreadPack;public class MyThreadDemo01 {public static void main(String[] args){MyThrea…

gpio 树莓派3a+_树莓派4上市:性能飙升起售价依然是35美元

旨在通过超实惠方式&#xff0c;鼓励孩子们投身编程事业的奇趣开发套件树莓派&#xff0c;在昨天迎来了Raspberry Pi 4正式开售的消息。新一代树莓派引入64位处理器、802.11ac双频Wi-Fi以及通过HAT的以太网供电(PoE)等新卖点。树莓派Raspberry Pi 4的处理能力是前一代的三倍、多…

入门级微单反性能对比

入门级相机筛选 先说挑选规则 由于微单体型小于单反&#xff0c;所以在机型选择上微单&#xff1e;单反&#xff1b;最好具备翻转屏和触摸屏&#xff1b;机身防抖不是刚需&#xff0c;但能解决小范围抖动情况下稳定问题&#xff1b;自动对焦很重要&#xff0c;眼控对焦是加分…

知识更新越来越快,但是学习起来越来越困

大家好&#xff0c;我是Z哥&#xff0c;先祝大家节日快乐。不知道这个假期你打算出门吗&#xff1f;Z哥我是打算不出远门了&#xff0c;怕死&#xff0c;哈哈。索性好好宅家里学习&#xff0c;强化一下自己。相信大家也感受到了&#xff0c;随着互联网加速了信息的流动速度&…

什么标准规定了aes加密_Python 爬虫进阶必备 | 关于某租房网站数据加密的分析(送两本 Python 书)...

关于某租房网站数据加密的分析aHR0cHM6Ly93d3cubWFvbWFvenUuY29tLw抓包分析先看看这个网站的首页数据可以看到首页的 html 是压缩的&#xff0c;但是格式化之后没有看到需要的首页数据。过滤 xhr 请求看到一个 index.json的请求可以看到这个请求的请求参数以及返回值都是密文返…

[Java基础]线程同步之卖票案列分析

案列: 卖票。 需求: 某电影院目前正在上映国产大片&#xff0c;共有100张票&#xff0c;而它有3个窗口卖票&#xff0c;请设计一个程序模拟该电影院卖票。 代码如下: package SellTicketPack;public class SellTicket implements Runnable{private int tickets 100;Overrid…

sap 标准委外和工序委外_SAP FICO零基础学习_0035_标准成本估算-主数据-物料主数据...

前辈的第35堂课&#xff1a;谢谢大家的喜欢和关注噢~这里的“前辈”其实指的是给我讲课的前辈啦&#xff0c;我不是前辈噢&#xff0c;我只是一个刚刚接触FICO的小白&#xff0c;跟大家分享前辈给我讲的东西。因为刚接触&#xff0c;学习的内容比较简单~有基础的小伙伴可以去看…