8

.NET GC 精要(三)

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/104091279
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

.NET GC 精要(三)

tkokof1 2020-01-27 10:50:13 295

本文讲述了 .NET GC 的一些细节知识,内容大部分来自于书籍 Under the Hood of .NET Memory Management
(注:本文假设你了解 .NET 的基础知识,譬如值类型,引用类型等)

.NET 为了处理非托管资源(unmanaged resource)的释放问题,引入了终结器(Finalization)的机制,相关的代码实现上也并不复杂,仅需要在类型中定义 析构函数 或者 Finalize 函数 即可(定义的 析构函数 或者 Finalize 函数会在对象"被清理"之后执行),示例代码如下:

// method 1
class TestClass1
{
    ~TestClass1()
    {
        // release unmanaged resource here
    }
}

// method 2
class TestClass2
{
    void Finalize()
    {
        // release unmanaged resource here
    }
}

那么 GC 是怎么执行这些自定义的"终结"函数(析构函数 或者 Finalize 函数)的呢?你可能会认为答案非常简单:在对象被清理的时候调用执行即可.这种方式的确是可行的,但是会降低 GC 的性能:在自定义的"终结"函数中,代码可能会执行数据库关闭等耗时操作,如果我们在 GC 流程中直接同步调用这些"终结"函数,那么整个 GC 流程就必须等待这些耗时操作的完成,性能上的损耗可想而知.

所以实际上, .NET 使用了与 GC 完全独立的一个线程来处理终结器机制,这个"终结器"线程会定期的执行被清理对象的"终结"函数,细节实现上则主要涉及两个队列: Finalization Queue 和 fReachable Queue.

首先,如果一个对象定义了"终结"函数,那么在创建该对象时,该对象的引用会被额外添加到 Finalization Queue 中,之后如果 GC 流程发现该对象已经没有被引用,正常情况下该对象就会被清理,但是由于 Finalization Queue 中还存在(对该对象的)引用的关系,该对象不仅不会被清理,反而可能会进行"代提升"(譬如从 Gen 1 提升到 Gen 2,更多细节可以看之前的讲述),另外的,该对象的引用会从 Finalization Queue 中清除,然后加入到 fReachable Queue 中,之后"终结器"线程便会执行 fReachable Queue 中引用对象的"终结"函数,然后清除 fReachable Queue 中的对象引用,之后该对象才能被 GC 流程真正清理.

还记的之前提过 GC roots(GC 根)吗? Finalization Queue 和 fReachable Queue 其实也可以算作 GC roots 之一.

上述流程的示意图:

在这里插入图片描述
在这里插入图片描述

(注意观察图中 Object Z(实现了"终结"函数) 的引用变化)

可以看到,终结器使对象的存在周期变长了,很多时候我们并不希望这种情况发生,缓解该问题的一个方法就是我们主动调用对象的"终结"函数,关于此有一个被称为 Dispose Pattern 的代码模式,之前有写过一些细节知识,有兴趣的朋友可以看看(值得一提的是,之前文章中提到的 GC.SuppressFinalize 函数,原理上其实就是将对象引用从 Finalization Queue 中清除,这样该对象就可以被 GC 正常清理了).

未完待续(to be continued)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK