本篇继续看下析构函数的一些引申知识。
析构函数目前发现的总共有三个标记,这里分别一一介绍下。先上一段代码:
internal class Program : IDisposable{ static void Main(string[] args){ StreamReader? streamReader = null; streamReader = new StreamReader("Test_.dll"); streamReader?.Dispose(); Console.ReadLine(); } ~Program(){ Console.WriteLine("调用了析构函数"); } public void Dispose(){ this.Dispose(); GC.SuppressFinalize(this); } }
这里的析构函数跟Dispose一起混用, ~Program()析构函数会通过Roslyn生成
.method family hidebysig virtual instance void Finalize() cil managed{ .override [System.Runtime]System.Object::Finalize // 代码大小 24 (0x18) .maxstack 1 IL_0000: nop .try { IL_0001: nop IL_0002: ldstr bytearray (03 8C 28 75 86 4E 90 67 84 67 FD 51 70 65 ) // ..(u.N.g.g.Qpe IL_0007: call void [System.Console]System.Console::WriteLine(string) IL_000c: nop IL_000d: leave.s IL_0017 } // end .try finally{ IL_000f: ldarg.0 IL_0010: call instance void [System.Runtime]System.Object::Finalize() IL_0015: nop IL_0016: endfinally } // end handler IL_0017: ret} // end of method Program::Finalize
这里同时需要注意 streamReader?.Dispose();这句话,streamreader实际上继承的是textreader
public class StreamReader : TextReader{}
所以它调用Dispose的代码是TextReader里面的Dispose:
public void Dispose() { Dispose(true); GC.SuppressFinalize(this); }
也就是关闭了streamReader流。然后base.Dispose.这个base.Dispose实际上就是它的父类TextReader里面的
public void Dispose(){ this._streamReader.close();}
Dispose里面的下面一句代码
GC.SuppressFinalize(this);
它是重点。
如果类里面有析构函数,比如例子里的Program,则会设置MethodTable的成员m_dwFlags
m_dwFlags |= enum_flag_HasFinalizer(0x00100000);
它的设置逻辑是如果存在析构函数,并且当前方法不是接口,不是虚方法,方法的索引小于当前类的索引数,当前的方法不是Object.Finlize()。那么说明当前这个类有析构函数,所以需要在当前类的MethodTable上进行操作,也即上面的m_dwFlags位设置。
逻辑代码如下:
//存在析构函数,并且当前方法不是接口,不是虚方法if (g_pObjectFinalizerMD && !IsInterface() && !IsValueClass()){ WORD slot = g_pObjectFinalizerMD->GetSlot(); //方法的索引小于当前类宗的索引数,当前的方法不是Object.Finlize() if (slot < bmtVT->cVirtualSlots && (*bmtVT)[slot].Impl().GetMethodDesc() != g_pObjectFinalizerMD) { GetHalfBakedMethodTable()->SetHasFinalizer(); //这个地方就是设置m_dwFlags //此处省略一万行 }}
headerobj|BIT_SBLK_FINALIZER_RUN当我们调用GC.SuppressFinalize的时候,它会进行判断m_dwFlags或上的enum_flag_HasFinalizer位是否为1,如果位0直接返回,如果为1,则设置对象头。它的判断逻辑如下
if (!obj->GetMethodTable ()->HasFinalizer())//HasFinalizer函数判断m_dwFlags的enum_flag_HasFinalizer位return;GCHeapUtilities::GetGCHeap()->SetFinalizationRun(obj);//这里设置当前类的对象头headerobj|BIT_SBLK_FINALIZER_RUNBIT_SBLK_FINALIZER_RUN定义如下:#define BIT_SBLK_FINALIZER_RUN 0x40000000
设置flags |= GC_ALLOC_FINALIZE一个对象需要进行空间的分配,当进行空间分配的时候,它会判断当前函数是否包含了析构函数。如果包含了,则设置flags标志最后一位位1.然后在对象分配的时候,把它放入到析构队列里面去。
if (pMT->HasFinalizer())//判断当前类是否包含析构函数 flags |= GC_ALLOC_FINALIZE;//如果包含则设置flags最后一位为1GC_ALLOC_FINALIZE定义如下:enum GC_ALLOC_FLAGS{ GC_ALLOC_NO_FLAGS = 0, GC_ALLOC_FINALIZE = 1, GC_ALLOC_CONTAINS_REF = 2, GC_ALLOC_ALIGN8_BIAS = 4, GC_ALLOC_ALIGN8 = 8, GC_ALLOC_ZEROING_OPTIONAL = 16, GC_ALLOC_LARGE_OBJECT_HEAP = 32, GC_ALLOC_PINNED_OBJECT_HEAP = 64, GC_ALLOC_USER_OLD_HEAP = GC_ALLOC_LARGE_OBJECT_HEAP | GC_ALLOC_PINNED_OBJECT_HEAP,};
当进行对象分配的时候,它会判断falgs最后一位是否为1,如果为1,则把对象放入到析构队列,不为1,则不放入。
CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(newAlloc, size, flags & GC_ALLOC_FINALIZE); //flags & GC_ALLOC_FINALIZE判断falgs最后一位是否为1.#define CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(_object, _size, _register) do { //这里的register就是flags & GC_ALLOC_FINALIZE的值,下面的逻辑如果对象为空直接返回,如果不为空则判断flags & GC_ALLOC_FINALIZE是否等于1,如果为零直接返回,如果为1,则调用REGISTER_FOR_FINALIZATION,把对象放入析构队列 if ((_object) == NULL || ((_register) && !REGISTER_FOR_FINALIZATION(_object, _size))) { STRESS_LOG_OOM_STACK(_size); return NULL; }
以上是析构函数,GC.SuppressFinalize,Dispose的最底层逻辑。当然这里还有很多技术问题需要解决。后面再看。
而BIT_SBLK_FINALIZER_RUN标记是最为重要的,它如果被标记了则表示从析构队列里面溢出,不需要运行这个当前类的析构函数。
在GC的标记阶段标记对象是否存活完成之后,它需要对对象的析构队列进行扫描。如果析构队列(SegQueue)里的对象被标记存活,且它的对象头有
BIT_SBLK_FINALIZER_RUN标志,则表示此对象的析构队列里的对象可以移出了,也就是不运行此对象的析构函数。
//这里的ScanForFinalization是在GCScanRoot之运行的,还有一个从析构函数里面取出//对象运行析构函数则是GCHeap::GetNextFinalizableObjectCFinalize::ScanForFinalization (promote_func* pfn, int gen, BOOL mark_only_p, gc_heap* hp){ //判断对象头是否标记了BIT_SBLK_FINALIZER_RUN if ((obj->GetHeader()->GetBits()) & BIT_SBLK_FINALIZER_RUN) { //如果标记了,则把这个对象移除到FreeList,也即是空闲的析构列表,不然存在于析构列表中 MoveItem (i, Seg, FreeList); //然后清除掉此对象头BIT_SBLK_FINALIZER_RUN标志 obj->GetHeader()->ClrBit (BIT_SBLK_FINALIZER_RUN); }}
本文链接:http://www.28at.com/showinfo-26-12734-0.html.Net析构函数再论(源码剖析)
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: Docker容器化构建可扩展的分布式缓存系统:Memcached和Redis
下一篇: 喝了100杯酱香拿铁,我开窍了