1.前言 N久之前搞过析构方面的问题,但是前几天又遇到了这个问题。本篇来巩固下析构方面的知识,包括初始化析构管理类,析构队列添加数据,以及调用析构函数。以下以.Net8 PreView为蓝本发掘,友情提示:C++和C#混合开发,慎入。
2.概述 先上例子:
internal class Program{
static void Main(string[] args){
Program pm = new Program();
pm = null;
GC.Collect();
}
~Program() {
Console.WriteLine("调用了析构函数");
}
}
初始化析构管理类
以上代码有一个~Program析构函数。CLR会在启动的时候初始化析构管理类
HRESULT AllocateCFinalize(CFinalize **pCFinalize)
{
*pCFinalize = new (nothrow) CFinalize();
if (*pCFinalize == NULL || !(*pCFinalize)->Initialize())
return E_OUTOFMEMORY;
return S_OK;
}
变量pCFinalize里面包含了指针填充数组m_FillPointers。
PTR_PTR_Object m_FillPointers[total_generation_count + ExtraSegCount]; //m_FillPointers[7]
它是一个object* 指针对象,长度是7.它的值填充的是析构队列的首地址,m_Array。也即是(* pCFinalize)->Initialize())调用填充。
简略图构
析构队列添加数据 上面初始化了析构管理类之后,就是往析构队列里面添加数据了。数据添加非常简单
CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(newAlloc, size, flags & GC_ALLOC_FINALIZE);
flags & GC_ALLOC_FINALIZE,可以看到flags标志&上析构标志,判断当前对象newAlloc是否添加到析构队列。
填充析构队列的主要代码:
Object*** s_i = &SegQueue (FreeList); //Freelist==7
Object*** end_si = &SegQueueLimit (dest);//dest == 4
do
{
if (!(*s_i == *(s_i-1))) //获取指针填充数据的第七个索引值和第六个索引值,判断是否不相等。
{
*(*s_i) = *(*(s_i-1)); //如果不相等就把指针填充数据第六个索引值所对应的对象赋值给第七个索引值对应的对象
}
(*s_i)++;//如果相等,继续下面的。这里把第7索引值+8,也就是析构队列的指针移动八位
s_i--;//这里把指针填充数据的指针向前挪动八位
} while (s_i > end_si);//判断是否到底了,也就是dest==4的地方。
**s_i = obj;//如果到底了,把有析构函数的对象添加到析构队列。
(*s_i)++;//因为这个地址已经添加过了,所以需要下一个地址,这里++
以上动用了三级指针,横跨了析构管理类,指针填充数据,以及最终存放的对象。
调用析构函数 调用析构是异步执行模型,它在初始化完毕析构管理类之后就开始被启动。
coreclr.dll!FinalizerThread::FinalizeAllObjects()
ntdll.dll!RtlUserThreadStart()
FinalizeAllObjects()主要代码如下:
void FinalizerThread::FinalizeAllObjects()
{
Object* fobj = GCHeapUtilities::GetGCHeap()->GetNextFinalizable();
Thread *pThread = GetThread();
while (fobj && !fQuitFinalizer)
{
fcount++;
CallFinalizer(fobj);
pThread->InternalReset();
fobj = GCHeapUtilities::GetGCHeap()->GetNextFinalizable();
}
FireEtwGCFinalizersEnd_V1(fcount, GetClrInstanceId());
}
它会判断从析构队列里面取出的对象是否为空,不为空就调用这个对象的析构函数CallFinalizer。