找資料時發現GC.GetTotalMemory()這個方法,看到有些人用它來測量記憶體使用狀況,我也好奇玩了一下,包成一個MemWatch Class(比照Stopwatch的概念):

//簡易版的記憶體用量觀察工具
//  透過觀察Managed Heap的總使用量變化
//  粗略推估某段程式所耗用的記憶體大小 
class MemWatch
{
    //比較記憶體使用量變化的基準值
    private long _lastTotalMemory = 0;
    //記憶體使用量變化
    public long MemorySizeChange = 0;
    //是否強制GC再測量記憶體用量
    private bool _forceGC = false;
    //可指定測量前是否要先做GC
    //(可排除己不用但尚未回收的記憶體)
    public MemWatch(bool forceGC)
    {
        _forceGC = forceGC;
    }
    public MemWatch() : this(false) { }
    //保留測量開始之基準
    public void Start()
    {
        _lastTotalMemory = 
            GC.GetTotalMemory(_forceGC);
    }
    //測量從Start()至今的記憶體變化
    public void Stop()
    {
        MemorySizeChange =
             GC.GetTotalMemory(_forceGC) - _lastTotalMemory;
    }
    //記憶體使用量變化(以KB計)
    public string MemorySizeChangeInKB
    {
        get
        {
            return string.Format("{0:N0}KB", 
                MemorySizeChange / 1024);
        }
    }
    //記憶體使用量變化(以MB計)
    public string MemorySizeChangeInMB
    {
        get
        {
            return string.Format("{0:N0}MB", 
                MemorySizeChange / 1024 / 1024);
        }
    }
}

測試時,故意宣告指定大小的byte[],看看結果是否如預期:

class Program
{
    static void Main(string[] args)
    {
        //測試1-宣告byte陣列記憶體使用
        //利用MemWatch觀察Heap記憶體使用狀況
        MemWatch mw = new MemWatch();
        //宣告前先記錄使用前的記憶體用量
        mw.Start();
        byte[] b1 = new byte[512 * 1024];
        //宣告後再測一次
        mw.Stop();
        //會得到二次的記體變化差距為512KB
        Console.WriteLine(mw.MemorySizeChangeInKB);
 
        //測試2-這次宣告byte陣列後立即棄用,但不做GC
        mw.Start();
        byte[] b2 = new byte[256 * 1024];
        b2 = null;
        mw.Stop();
        //b2佔用記憶體已釋出,但尚未回收,結果為256KB
        Console.WriteLine(mw.MemorySizeChangeInKB);
        
        //進行下一階段測試前先強制GC
        GC.Collect();
 
        //測試3-同2,但換成先GC才測記憶體大小的測量模式
        mw = new MemWatch(true);
        mw.Start();
        byte[] b3 = new byte[256 * 1024];
        b3 = null;
        mw.Stop();
        //MemWatch測量時會先將b2釋出的記憶體回收,故結果為0KB
        Console.WriteLine(mw.MemorySizeChangeInKB);
 
        Console.Read();
    }
}

補充:

  1. GC.GetTotalMemory()只能取得Managed Heap部分的大小,若變數耗用的是Stack部分的記憶體(例如: Value Type),則不在量測範圍內。
  2. GetTotalMemeory()只能量測總耗用量,無法精準指出特定變數/物件所耗用記憶體大小。
  3. MemWatch預設會包含配置後棄用但尚未被GC機制回收的記憶體量,若要強制回收可自行呼叫GC.Collect()或用MemWatch(true)建構式啟用強制回收模式。注意,這裡是因測試需要才自行操作GC回收,實務上應避免在程式中自行呼叫GC.Collect(),最好交由.NET Runtime自行決定何時進行回收,較有利於整體效能。
  4. Compiler常會針對程式碼進行最佳化(例如: 忽略只宣告但完全未使用的物件、改變程式執行順序以改善效率...等等),因此當執行結果與預期出入很大時,可研究看看是否與最佳化有關。

Comments

# by licicous0421

最近剛好有遇到記憶體釋放的問題! 想請問一下~ 測試3 最後面 //MemWatch測量時會先將b2釋出的記憶體回收,故結果為0KB 可是b3生成的空間不是還未被回收嗎? 這樣測出來因該是256KB??

# by Jeffrey

to licicous0421, 測試3在mw.Stop()前有b3=null,而在Stop()中MemorySizeChange = GC.GetTotalMemory(_forceGC) - _lastTotalMemory;的_forceGC==true,會先回收b3使用記憶體再測量記憶體用量,故結果為0KB。

# by Jason

我以前有遇到一個問題是,我試過做一個視窗,裡面沒功能, 只是放著沒做什麼事 在工作管理員中每隔一段時間就會看到記憶體使用量增加了一些 慢慢的增加,記憶體越用越多 這是為什麼?

Post a comment