GC和解構函式(Finalize 方法)

風靈使發表於2018-06-28

解構函式:

(來自百度百科)解構函式(destructor) 與建構函式相反,當物件脫離其作用域時(例如物件所在的函式已呼叫完畢),系統自動執行解構函式。解構函式往往用來做“清理善後” 的工作(例如在建立物件時用new開闢了一片記憶體空間,應在退出前在解構函式中用delete釋放)。

C#中的解構函式定義與C++ 類似,~+函式名的方法:

public class FinalizeClass
{
    ~FinalizeClass()
    {
        //在這裡,清理非託管資源
    }
}

生成的IL程式碼:

.class public auto ansi beforefieldinit Test.FinalizeClass
    extends [mscorlib]System.Object
{
    // Methods
    .method family hidebysig virtual 
        instance void Finalize () cil managed 
    {
        // Method begins at RVA 0x2070
        // Code size 25 (0x19)
        .maxstack 1

        .try
        {
            IL_0000: nop
            IL_0001: ldstr "FinalizeClass的解構函式"
            IL_0006: call void [mscorlib]System.Console::WriteLine(string)
            IL_000b: nop
            IL_000c: nop
            IL_000d: leave.s IL_0017
        } // end .try
        finally
        {
            IL_000f: ldarg.0
            IL_0010: call instance void [mscorlib]System.Object::Finalize()
            IL_0015: nop
            IL_0016: endfinally
        } // end handler

        IL_0017: nop
        IL_0018: ret
    } // end of method FinalizeClass::Finalize

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20a8
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method FinalizeClass::.ctor

} // end of class Test.FinalizeClass

實際上生成了一個Finalize方法,內部呼叫了Base.Finalize()方法,也就是ObjectFinalize 方法。

Finalize方法只能由GC呼叫,我們是不能呼叫的。下面說下GC呼叫Finalize的流程!

Finalization List(Queue)(終結列表)

我們new 一個物件,如果這個物件包含Finalize方法,開闢記憶體後,指向它的指標會被存放到終結列表中(Object物件除外)。

Freachable Queue (F-reachable終結可到達佇列)

垃圾回收開始時,被判定為垃圾(不可達)的物件如果同時存在於Finalization List中,就會將該物件的指標從Finalization List移除,並存入Freachable Queue中。同時這些物件都變為可達(reachable),不會被GC回收,這樣就意味著這些物件提升到另一代,這裡假設為2代物件。

該佇列中的物件都是可達的,並需要執行Finalize方法。執行Finalize方法是由一個高優先順序的CLR執行緒進行的,執行完畢後,會將物件的指標從Freachable Queue中移除(當該佇列為空時,此執行緒將睡眠,在不為空時被喚醒)。

當再次進行垃圾回收時,原Freachable Queue中的物件經過處理都變為不可達物件(2代),只有當2代記憶體不足時才會對2代物件進行垃圾回收,這些物件記憶體才會真正釋放掉。因此含有Finalize方法的物件最少要經過兩次垃圾回收才會被真正釋放。

看圖解:
這裡寫圖片描述
物件2、3、5、6、10包含Finalize方法,2、5、7、9為不可達物件(GC的目標)。
這裡寫圖片描述
進行GC時,由於2、5物件包含Finalize方法,因此被放入Freachable Queue中,變為可達物件並提升代,不進行垃圾回收。而物件7、9直接被回收。
這裡寫圖片描述
如果原Freachable所在代進行GC,就會回收物件2、5的記憶體。

結論

1.含有Finalize方法的物件最少要經過兩次垃圾回收才會被真正釋放。

2.如非必要,不建議定義Finalize方法(用Dispose模式替代)。

相關文章