.Net析構函數再論(源碼剖析)
1.前言
本篇繼續看下析構函數的一些引申知識。
2.概述
析構函數目前發現的總共有三個標記,這里分別一一介紹下。先上一段代碼:
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);
它是重點。
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的最底層邏輯。當然這里還有很多技術問題需要解決。后面再看。
標記的作用
而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_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);
}
}