CLR析构列表是如何添加析构函数类的

960 阅读2分钟

比如说,有一个类,包含了析构函数

  class A
    {
        public A()
        {
            Console.WriteLine("Create A Class");
        }
        ~A()
        {
            Console.WriteLine("Kill A Class");
        }
    }
    

当我们实例化这个类的时候

    A a = new A()

CLR在分配a这个实例的时候,会检测它是否包含了析构函数~A。

CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(newAlloc, size, flags & GC_ALLOC_FINALIZE);


#define CHECK_ALLOC_AND_POSSIBLY_REGISTER_FOR_FINALIZATION(_object, _size, _register) do {  \
    if ((_object) == NULL || ((_register) && !REGISTER_FOR_FINALIZATION(_object, _size)))   \
    {                                                                                       \
        STRESS_LOG_OOM_STACK(_size);                                                        \
        return NULL;                                                                        \
    }                                                                                       \
} while (false)

注意看这个宏的if 判断语句,它首先是个do-while循环,这个很有意思,wihile包含的条件就是个false,表示它只循环一次。其次会判断_object也就是传递进来的参数newAlloc。是否为NULL,如果是直接返回NULL,因为一个NULL就没必要进行后续动作了。

当它不等于NULL,后续需要_register和 !REGISTER_FOR_FINALIZATION(_object, _size))这两个条件。_register实际上就是判断当前的实例化分配的类A是否包含析构函数操作为:flags & GC_ALLOC_FINALIZE。

而!REGISTER_FOR_FINALIZATION(_object, _size))是重点,它包含了如何把析构函数的对象添加到析构列表。

实际上因为 ((_register) && !REGISTER_FOR_FINALIZATION(_object, _size))这两个是&& ,所以_register就算是包含了析构函数,但是REGISTER_FOR+FINALIZATION返回True。它还是不进入If语句里面去。

重点关注REGISTER_FOR_FINALIZATION

#define REGISTER_FOR_FINALIZATION(_object, _size) \
    hp->finalize_queue->RegisterForFinalization (0, (_object), (_size))

它实际上也是个宏,调用了RegisterForFinalization 函数,这个finalize_queue是在hp(hp 就是gc_heap)初始化的时候被Init的。

finallize_queue实例化代码:

bool CFinalize::Initialize()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
    } CONTRACTL_END;


    m_Array = new (nothrow)(Object*[100]);


    if (!m_Array)
    {
        ASSERT (m_Array);
        STRESS_LOG_OOM_STACK(sizeof(Object*[100]));
        if (GCConfig::GetBreakOnOOM())
        {
            GCToOSInterface::DebugBreak();
        }
        return false;
    }
    m_EndArray = &m_Array[100];


    for (int i =0; i < FreeList; i++)
    {
        SegQueueLimit (i) = m_Array;
    }
    m_PromotedCount = 0;
    lock = -1;
#ifdef _DEBUG
    lockowner_threadid.Clear();
#endif // _DEBUG


    return true;
}

实际上就做了一件事情,就是让SegQueueLimit的每一个元素指向析构列表

我们来看RegisterForFinalization

CFinalize::RegisterForFinalization (int gen, Object* obj, size_t size)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
    } CONTRACTL_END;


    EnterFinalizeLock();


    // Adjust gen
    unsigned int dest = gen_segment (gen);


    // Adjust boundary for segments so that GC will keep objects alive.
    Object*** s_i = &SegQueue (FreeList);
    if ((*s_i) == m_EndArray)
    {
        if (!GrowArray())
        {
            LeaveFinalizeLock();
            if (method_table(obj) == NULL)
            {
                // If the object is uninitialized, a valid size should have been passed.
                assert (size >= Align (min_obj_size));
                dprintf (3, (ThreadStressLog::gcMakeUnusedArrayMsg(), (size_t)obj, (size_t)(obj+size)));
                ((CObjectHeader*)obj)->SetFree(size);
            }
            STRESS_LOG_OOM_STACK(0);
            if (GCConfig::GetBreakOnOOM())
            {
                GCToOSInterface::DebugBreak();
            }
            return false;
        }
    }
    Object*** end_si = &SegQueueLimit (dest);
    do
    {
        //is the segment empty?
        if (!(*s_i == *(s_i-1)))
        {
            //no, swap the end elements.
            *(*s_i) = *(*(s_i-1));
        }
        //increment the fill pointer
        (*s_i)++;
        //go to the next segment.
        s_i--;
    } while (s_i > end_si);


    // We have reached the destination segment
    // store the object
    **s_i = obj;
    // increment the fill pointer
    (*s_i)++;


    LeaveFinalizeLock();


    return true;
}

这个函数做的主要功能

  1. 获取到当前传递进来的代在m_FillPointers数组的索引4(因为传递进来的代只能是0,所以total_generation_count - gen - 1=4,调用unsigned int dest = gen_segment (gen);)

  2. 获取到m_FillPointers的最末尾元素的地址Object*** s_i = &SegQueue (FreeList),FreeList=7

  3. 获取m_FillPointers索引为N的元素地址 Object*** end_si = &SegQueueLimit (dest)

  4. 判断前一个m_FillPointers的元素是否与当前元素相等。

  5. m_FillPointers元素的值取值,实际上就是指向Object的指针。然后++,实际上就是m_array+8.指向析构队列的下一个元素。

  6. 如此往复循环,找到当前析构对象需要存放的位置。

当GC的时候

  1. 先判断对象是否存在
  2. 执行析构对象里面的方法
  3. 回收掉它