about 7 years ago

整理 .NET Framework 中的記憶體釋放機制。

Managed vs Unmanaged Resources


首先要先了解託管資源(managed resources)與非託管資源(unmanaged resources)間的差異。託管資源指的是 GC 管理的記憶體空間,當沒有任何參考指向託管資源時,GC 會在適當的時機自動釋放它。而非託管資源則是 GC 不知道的記憶體空間,例如:windows handles(HWND)、database connections 等,使用者必須自行釋放。

Destructor 與 Finalize 間的關係


Finalize(); 不應該直接被調用,而是由類別的 destructor 隱含呼叫。

~MyClass()
{
    DoReleaseMyResource();
}

解構函式會被轉換為以下代碼:

protected override void Finalize()
{
    try
    {
        DoReleaseMyResource();
    }
    finally
    {
        base.Finalize();
    }
}

IDisposable


將記憶體空間交由 GC 控管雖然方便,但缺點是釋放時機無法自行掌握,因此 .Net 提供了 IDisposable 介面,提供及時釋放資源的機制。

IDisposable 中只公開了一個方法 Dispose(),官方提供了標準的實作方式如下:

// Design pattern for a base class.
public class Base: IDisposable
{
    private bool disposed = false;

    //Implement IDisposable.
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free other state (managed objects).
            }

            // Free your own state (unmanaged objects).
            // Set large fields to null.
            disposed = true;
        }
    }

    // Use C# destructor syntax for finalization code.
    ~Base()
    {
        // Simply call Dispose(false).
        Dispose (false);
    }
}

// Design pattern for a derived class.
public class Derived: Base
{
    private bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Release managed resources.
            }

            // Release unmanaged resources.
            // Set large fields to null.
            // Call Dispose on your base class.
            disposed = true;
        }

        base.Dispose(disposing);
    }

    // The derived class does not have a Finalize method
    // or a Dispose method without parameters because it inherits
    // them from the base class.
}

IDisposable 實作分析


範例程式碼中有幾個重點:

  1. disposed 旗標判斷是否做過資源釋放的動作。

    public class Base: IDisposable
    {
        private bool disposed = false;
    
        ...
    
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                // Free your own state (unmanaged objects).
                // Set large fields to null.
                disposed = true;
            }
        }
    }
    

    透過 Dispose(); 釋放資源後,此資源仍可能短暫存在記憶體中,其他物件如果仍有此資源的參考,還是可能發生錯誤引用,所以需要 disposed 旗標來把關。

  2. 覆寫 void Dispose(bool disposing)。Dispose 方法有兩個可能的進入點:

    • 由呼叫端自行呼叫 Dispose(); 方法
    • 由 GC 觸發 destructor,再由 destructor 呼叫 Dispose();

    如果是由使用者呼叫 Dispose 方法,就要自行釋放託管資源。相反地,若是由 GC 觸發,物件中的託管資源已被 GC 釋放,需避免重複釋放資源。

  3. Dispose 方法中加上 GC.SuppressFinalize(this);,表示資源已由物件自行釋放,不會進入解構函式(Finalize())。

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    

References


← Add Git Auto Completion in Bash Prompt Dalvik VM and Heap →
 
comments powered by Disqus