C# 自動內(nèi)存管理

2018-09-28 18:42 更新

自動內(nèi)存管理

C# 使用(employs)自動內(nèi)存管理(automatic memory management),這解放了開發(fā)人員必須手動分配與釋放對象內(nèi)存戰(zhàn)勇的麻煩。自動內(nèi)存管理策略由垃圾回收器(garbage collector)實現(xiàn)。對象的內(nèi)存管理生命周期如下:

  1. 當(dāng)對象被創(chuàng)建時,為它分配內(nèi)存,運行構(gòu)造函數(shù),視該對象為存活(live)對象。
  2. 如果一個對象(或其任何一個部分)不能在后續(xù)執(zhí)行(continuation of execution)中被訪問——除了析構(gòu)函數(shù)——那么這個對象將被視為不再使用并符合銷毀條件(eligible for destruction)。C# 編譯器和垃圾回收器會選擇分析代碼,并確定哪個對象的引用在未來會被使用。比方說,如果某范圍內(nèi)某個局部變量是某對象的唯一存在的引用,但在當(dāng)前執(zhí)行點(current execution point)之后的所有過程中(procedure)都不會再引用這個變量,那么垃圾回收器可能(但不是絕對)會理解為這個對象不再被使用。
  3. 一旦對象符合銷毀條件(eligible for destruction),那么在稍后某個時間(at some unspecified later time)將運行這個對象的析構(gòu)函數(shù)(第十章第十三節(jié))(如果有析構(gòu)函數(shù)的話)。在正常情況下,對象的析構(gòu)函數(shù)只會運行一次(once only),但特定實現(xiàn)(implementation-specific)的 APIs 可能會允許忽視(overridden)這個行為。
  4. 一旦運行該對象的析構(gòu)函數(shù),那么這個對象(或其任何一部分)都不能在后續(xù)執(zhí)行中被訪問到——包括運行中的析構(gòu)函數(shù)——這個對象將被視為不可訪問(inaccessible)并符合回收條件(eligible for collection)。
  5. 最后,在該對象符合回收條件后的某個時刻,垃圾回收器釋放分配給該對象的內(nèi)存。

垃圾回收器維護著對象使用(object usage)的信息,透過這些信息為內(nèi)存管理作出決策,諸如何處內(nèi)存存放了新建對象,何時遷移(relocate)一個對象以及一個對象何時開始不被使用或不可訪問。

和其它有垃圾回收器的語言類似,C# 的設(shè)計也在努力使垃圾回收器能實現(xiàn)更廣泛的(wide range)內(nèi)存管理策略(memory management policies)比方說,C# 并不強求析構(gòu)函數(shù)一定要運行,并不強求對象一滿足條件就要立即被回收,并不強求析構(gòu)函數(shù)一定要按照某個特定順序執(zhí)行或是在某個特定線程上執(zhí)行。

垃圾回收器的行為是可控的,在某種程度上講,可以通過 System.GC 上的靜態(tài)方法(來實現(xiàn))。通過這個類可以請求執(zhí)行一次回收操作、運行(或不運行)析構(gòu)函數(shù),等。

由于垃圾回收器在決定「何時回收(collect)對象并執(zhí)行析構(gòu)函數(shù)」這一點上充分自由(allowed wide latitude),因此一個合格的實現(xiàn)也許會產(chǎn)生與下面所示代碼有所不同的輸出。在程序

using System;
class A
{
    ~A() {
        Console.WriteLine("Destruct instance of A");
    }
}
class B
{
    object Ref;
    public B(object o) {
        Ref = o;
    }
    ~B() {
        Console.WriteLine("Destruct instance of B");
    }
}
class Test
{
    static void Main() {
        B b = new B(new A());
        b = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

中,創(chuàng)建了 A 和 B 的實例。當(dāng) null 值被賦予變量 b 之后,這些資源成為符合回收條件,這是由于自此之后用戶所寫的任何代碼都不可能訪問到它們。輸出的結(jié)果可能是

Destruct instance of A
Destruct instance of B

或者

Destruct instance of B
Destruct instance of A

,這是由于語言并未對對象被垃圾回收的順序強加限制。

盡管很相近,但區(qū)別「符合銷毀條件(eligible for destruction)」與「符合回收條件(eligible for collection)」還是比較重要的,比方說:

using System;
class A
{
    ~A() {
        Console.WriteLine("Destruct instance of A");
    }
    public void F() {
        Console.WriteLine("A.F");
        Test.RefA = this;
    }
}
class B
{
    public A Ref;
    ~B() {
        Console.WriteLine("Destruct instance of B");
        Ref.F();
    }
}
class Test
{
    public static A RefA;
    public static B RefB;
    static void Main() {
        RefB = new B();
        RefA = new A();
        RefB.Ref = RefA;
        RefB = null;
        RefA = null;
        // A and B now eligible for destruction
        GC.Collect();
        GC.WaitForPendingFinalizers();
        // B now eligible for collection, but A is not
        if (RefA != null)
            Console.WriteLine("RefA is not null");
    }
}

在上面程序中,如果立即回收器(garbage collector)選擇在運行 B 的析構(gòu)函數(shù)前先運行 A 的析構(gòu)函數(shù),那么這個程序也許會輸出:

Destruct instance of A
Destruct instance of B
A.F
RefA is not null

注意,盡管 A 的實例未被使用,依舊調(diào)用了 A 的析構(gòu)函數(shù),同時 A 的方法也有可能被其它析構(gòu)函數(shù)調(diào)用(此例中為 F)。同時還要注意,析構(gòu)函數(shù)的運行可能導(dǎo)致對象在主程序中再次變的可訪問。在此例中,B 析構(gòu)函數(shù)的運行導(dǎo)致了先前并未使用的 A 實例在通過引用 Test.RefA 時變得再次可訪問。在調(diào)用 WaitForPendingFinalizers 之后,B 的實例便符合回收條件了,但由于引用了 Test.RefA,所以實例 A 還不能被回收。

為了避免混淆和意外行為,建議類的析構(gòu)函數(shù)僅對自己內(nèi)部的字段數(shù)據(jù)進行清理,不要去干涉其它所引用的對象或靜態(tài)字段。

另一個替代析構(gòu)函數(shù)的方法是讓類實現(xiàn) System.IDisposable 接口。這將允許對象的客戶端(client of the object)決定何時釋放自身資源,通常會在 using 語句(第八章第十三節(jié))通過資源的方式訪問該對象。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號