前言
碰到一些问题,发觉依旧没有全面了解完全析构函数。本篇继续看下析构函数的一些引申知识。
概述
析构函数目前发现的总共有三个标记,这里分别一一介绍下。先上一段代码:
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 1IL_0000: nop.try{IL_0001: nopIL_0002: ldstr bytearray (03 8C 28 75 86 4E 90 67 84 67 FD 51 70 65 ) // ..(u.N.g.g.QpeIL_0007: call void [System.Console]System.Console::WriteLine(string)IL_000c: nopIL_000d: leave.s IL_0017} // end .tryfinally{IL_000f: ldarg.0IL_0010: call instance void [System.Runtime]System.Object::Finalize()IL_0015: nopIL_0016: endfinally} // end handlerIL_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);
它是重点。
GC.SuppressFinalize
1.判断当前类是否有析构函数
如果类里面有析构函数,比如例子里的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//此处省略一万行}
}
2.调用GC.SuppressFinalize
设置当前类的对象头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_RUN
BIT_SBLK_FINALIZER_RUN定义如下:
#define BIT_SBLK_FINALIZER_RUN 0x40000000
3.对象进行分配空间的时候
设置flags |= GC_ALLOC_FINALIZE
一个对象需要进行空间的分配,当进行空间分配的时候,它会判断当前函数是否包含了析构函数。如果包含了,则设置flags标志最后一位位1.然后在对象分配的时候,把它放入到析构队列里面去。
if (pMT->HasFinalizer())//判断当前类是否包含析构函数flags |= GC_ALLOC_FINALIZE;//如果包含则设置flags最后一位为1
GC_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的最底层逻辑。当然这里还有很多技术问题需要解决。后面再看。
标记的作用
GC.SuppressFinalize问题来了,它的这些标记有什么用呢?这是一个非常绕的问题,分析下。首先的enum_flag_HasFinalizer标记表示当前类包含了析构函数,GC_ALLOC_FINALIZE标记表示当前的类对象需要填充到析构队列里面去。而BIT_SBLK_FINALIZER_RUN标记是最为重要的,它如果被标记了则表示从析构队列里面溢出,不需要运行这个当前类的析构函数。
在GC的标记阶段标记对象是否存活完成之后,它需要对对象的析构队列进行扫描。如果析构队列(SegQueue)里的对象被标记存活,且它的对象头有
BIT_SBLK_FINALIZER_RUN标志,则表示此对象的析构队列里的对象可以移出了,也就是不运行此对象的析构函数。
//这里的ScanForFinalization是在GCScanRoot之运行的,还有一个从析构函数里面取出
//对象运行析构函数则是GCHeap::GetNextFinalizableObject
CFinalize::ScanForFinalization (promote_func* pfn, int gen, BOOL mark_only_p,gc_heap* hp)
{ //判断对象头是否标记了BIT_SBLK_FINALIZER_RUNif ((obj->GetHeader()->GetBits()) & BIT_SBLK_FINALIZER_RUN){//如果标记了,则把这个对象移除到FreeList,也即是空闲的析构列表,不然存在于析构列表中MoveItem (i, Seg, FreeList);//然后清除掉此对象头BIT_SBLK_FINALIZER_RUN标志obj->GetHeader()->ClrBit (BIT_SBLK_FINALIZER_RUN);}
}
欢迎关注我的公众号:jianghupt,后台回复:dotnet7。获取一套.Net7 CLR源码教程。顶级技术分享,文章首发。