经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
.Net析构函数再论(CLR源码级的剖析)
来源:cnblogs  作者:江湖评谈  时间:2023/10/9 9:59:04  对本文有异议

前言

碰到一些问题,发觉依旧没有全面了解完全析构函数。本篇继续看下析构函数的一些引申知识。

概述

析构函数目前发现的总共有三个标记,这里分别一一介绍下。先上一段代码:

  1. internal class Program : IDisposable{
  2. static void Main(string[] args){
  3. StreamReader? streamReader = null;
  4. streamReader = new StreamReader("Test_.dll");
  5. streamReader?.Dispose();
  6. Console.ReadLine();
  7. }
  8. ~Program(){
  9. Console.WriteLine("调用了析构函数");
  10. }
  11. public void Dispose(){
  12. this.Dispose();
  13. GC.SuppressFinalize(this);
  14. }
  15. }

这里的析构函数跟Dispose一起混用, ~Program()析构函数会通过Roslyn生成

  1. .method family hidebysig virtual instance void
  2. Finalize() cil managed
  3. {
  4. .override [System.Runtime]System.Object::Finalize
  5. // 代码大小 24 (0x18)
  6. .maxstack 1
  7. IL_0000: nop
  8. .try
  9. {
  10. IL_0001: nop
  11. IL_0002: ldstr bytearray (03 8C 28 75 86 4E 90 67 84 67 FD 51 70 65 ) // ..(u.N.g.g.Qpe
  12. IL_0007: call void [System.Console]System.Console::WriteLine(string)
  13. IL_000c: nop
  14. IL_000d: leave.s IL_0017
  15. } // end .try
  16. finally
  17. {
  18. IL_000f: ldarg.0
  19. IL_0010: call instance void [System.Runtime]System.Object::Finalize()
  20. IL_0015: nop
  21. IL_0016: endfinally
  22. } // end handler
  23. IL_0017: ret
  24. } // end of method Program::Finalize

这里同时需要注意 streamReader?.Dispose();这句话,streamreader实际上继承的是textreader

  1. public class StreamReader : TextReader
  2. {}

所以它调用Dispose的代码是TextReader里面的Dispose:

  1. public void Dispose()
  2. {
  3. Dispose(true);
  4. GC.SuppressFinalize(this);
  5. }

也就是关闭了streamReader流。然后base.Dispose.这个base.Dispose实际上就是它的父类TextReader里面的

  1. public void Dispose()
  2. {
  3. this._streamReader.close();
  4. }

Dispose里面的下面一句代码

  1. GC.SuppressFinalize(this);

它是重点。

GC.SuppressFinalize

1.判断当前类是否有析构函数
如果类里面有析构函数,比如例子里的Program,则会设置MethodTable的成员m_dwFlags

  1. m_dwFlags |= enum_flag_HasFinalizer(0x00100000);

它的设置逻辑是如果存在析构函数,并且当前方法不是接口,不是虚方法,方法的索引小于当前类宗的索引数,当前的方法不是Object.Finlize()。那么说明当前这个类有析构函数,所以需要在当前类的MethodTable上进行操作,也即上面的m_dwFlags位设置。
逻辑代码如下:

  1. //存在析构函数,并且当前方法不是接口,不是虚方法
  2. if (g_pObjectFinalizerMD && !IsInterface() && !IsValueClass())
  3. {
  4. WORD slot = g_pObjectFinalizerMD->GetSlot();
  5. //方法的索引小于当前类宗的索引数,当前的方法不是Object.Finlize()
  6. if (slot < bmtVT->cVirtualSlots && (*bmtVT)[slot].Impl().GetMethodDesc() != g_pObjectFinalizerMD)
  7. {
  8. GetHalfBakedMethodTable()->SetHasFinalizer(); //这个地方就是设置m_dwFlags
  9. //此处省略一万行
  10. }
  11. }

2.调用GC.SuppressFinalize
设置当前类的对象头headerobj|BIT_SBLK_FINALIZER_RUN
当我们调用GC.SuppressFinalize的时候,它会进行判断m_dwFlags或上的enum_flag_HasFinalizer位是否为1,如果位0直接返回,如果为1,则设置对象头。它的判断逻辑如下

  1. if (!obj->GetMethodTable ()->HasFinalizer())//HasFinalizer函数判断m_dwFlags的enum_flag_HasFinalizer位
  2. return;
  3. GCHeapUtilities::GetGCHeap()->SetFinalizationRun(obj);//这里设置当前类的对象头headerobj|BIT_SBLK_FINALIZER_RUN
  4. BIT_SBLK_FINALIZER_RUN定义如下:
  5. #define BIT_SBLK_FINALIZER_RUN 0x40000000

3.对象进行分配空间的时候
设置flags |= GC_ALLOC_FINALIZE
一个对象需要进行空间的分配,当进行空间分配的时候,它会判断当前函数是否包含了析构函数。如果包含了,则设置flags标志最后一位位1.然后在对象分配的时候,把它放入到析构队列里面去。

  1. if (pMT->HasFinalizer())//判断当前类是否包含析构函数
  2. flags |= GC_ALLOC_FINALIZE;//如果包含则设置flags最后一位为1
  3. GC_ALLOC_FINALIZE定义如下:
  4. enum GC_ALLOC_FLAGS
  5. {
  6. GC_ALLOC_NO_FLAGS = 0,
  7. GC_ALLOC_FINALIZE = 1,
  8. GC_ALLOC_CONTAINS_REF = 2,
  9. GC_ALLOC_ALIGN8_BIAS = 4,
  10. GC_ALLOC_ALIGN8 = 8,
  11. GC_ALLOC_ZEROING_OPTIONAL = 16,
  12. GC_ALLOC_LARGE_OBJECT_HEAP = 32,
  13. GC_ALLOC_PINNED_OBJECT_HEAP = 64,
  14. GC_ALLOC_USER_OLD_HEAP = GC_ALLOC_LARGE_OBJECT_HEAP | GC_ALLOC_PINNED_OBJECT_HEAP,
  15. };

当进行对象分配的时候,它会判断falgs最后一位是否为1,如果为1,则把对象放入到析构队列,不为1,则不放入。

  1. CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(newAlloc, size, flags & GC_ALLOC_FINALIZE); //flags & GC_ALLOC_FINALIZE判断falgs最后一位是否为1.
  2. #define CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(_object, _size, _register) do {
  3. //这里的register就是flags & GC_ALLOC_FINALIZE的值,下面的逻辑如果对象为空直接返回,如果不为空则判断flags & GC_ALLOC_FINALIZE是否等于1,如果为零直接返回,如果为1,则调用REGISTER_FOR_FINALIZATION,把对象放入析构队列
  4. if ((_object) == NULL || ((_register) && !REGISTER_FOR_FINALIZATION(_object, _size)))
  5. {
  6. STRESS_LOG_OOM_STACK(_size);
  7. return NULL;
  8. }

以上是析构函数,GC.SuppressFinalize,Dispose的最底层逻辑。当然这里还有很多技术问题需要解决。后面再看。

标记的作用

GC.SuppressFinalize问题来了,它的这些标记有什么用呢?这是一个非常绕的问题,分析下。首先的enum_flag_HasFinalizer标记表示当前类包含了析构函数,GC_ALLOC_FINALIZE标记表示当前的类对象需要填充到析构队列里面去。而BIT_SBLK_FINALIZER_RUN标记是最为重要的,它如果被标记了则表示从析构队列里面溢出,不需要运行这个当前类的析构函数。

在GC的标记阶段标记对象是否存活完成之后,它需要对对象的析构队列进行扫描。如果析构队列(SegQueue)里的对象被标记存活,且它的对象头有

BIT_SBLK_FINALIZER_RUN标志,则表示此对象的析构队列里的对象可以移出了,也就是不运行此对象的析构函数。

  1. //这里的ScanForFinalization是在GCScanRoot之运行的,还有一个从析构函数里面取出
  2. //对象运行析构函数则是GCHeap::GetNextFinalizableObject
  3. CFinalize::ScanForFinalization (promote_func* pfn, int gen, BOOL mark_only_p,
  4. gc_heap* hp)
  5. {
  6. //判断对象头是否标记了BIT_SBLK_FINALIZER_RUN
  7. if ((obj->GetHeader()->GetBits()) & BIT_SBLK_FINALIZER_RUN)
  8. {
  9. //如果标记了,则把这个对象移除到FreeList,也即是空闲的析构列表,不然存在于析构列表中
  10. MoveItem (i, Seg, FreeList);
  11. //然后清除掉此对象头BIT_SBLK_FINALIZER_RUN标志
  12. obj->GetHeader()->ClrBit (BIT_SBLK_FINALIZER_RUN);
  13. }
  14. }

欢迎关注我的公众号:jianghupt,后台回复:dotnet7。获取一套.Net7 CLR源码教程。顶级技术分享,文章首发。
image

原文链接:https://www.cnblogs.com/tangyanzhi1111/p/17749462.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号