1. 程式人生 > 其它 >用 .NET Memory Profiler 跟蹤.net 應用記憶體使用情況--基本應用篇

用 .NET Memory Profiler 跟蹤.net 應用記憶體使用情況--基本應用篇

用 .NET Memory Profiler 跟蹤.net 應用記憶體使用情況--基本應用篇

 

作者:肖波
      .net 框架號稱永遠不會發生記憶體洩漏,原因是其引入了記憶體回收的機制。但實際應用中,往往我們分配了物件但沒有釋放指向該物件的引用,導致物件永遠無法釋放。最常見的情況就是給物件添加了事件處理函式,但當不再使用該物件時卻沒有將該函式從物件的事件handler中減掉。另外如果分配了非託管記憶體,而沒有手工釋放,GC同樣無能為力。所以當.net應用發生記憶體洩漏後如何跟蹤應用的記憶體使用情況,定位到程式設計中的缺陷顯得非常重要。本文將介紹通過.NET Memory Profiler來跟蹤.net應用的記憶體洩漏,為定位.net應用記憶體問題提供一個解決途徑。

     .NET Memory Profiler是一款強大的.net 記憶體跟蹤和優化工具。該工具目前可以對一下4種.net應用進行記憶體跟蹤。

  • 基本應用 例如winform, console application等
  • ASP.net 應用
  • WPF應用
  • Window 服務

     本篇將通過對以下三種記憶體的跟蹤來闡述如何使用該工具對基本.net應用程式進行記憶體的跟蹤。三種記憶體包括:

  • 託管記憶體
  • 執行緒託管記憶體
  • 非託管記憶體

 

在開始之前,先需要建立環境。
       我採用.NET Memory Profiler V3.1.307 版本進行測試。安裝完後需要新建一個專案,由於我們需要測
.net基本應用,所以新建專案時選擇Standalone application. 點選next後,輸入要測試的.net 應用的路徑和引數。
然後按下 finish.專案就建立完成了。

       測試程式是我編寫的,編譯後生成TestMemorySize.exe 這個控制檯應用程式。下載地址

       程式碼如下

 

     主程式等待使用者輸入,輸入m,t,u 分別是增加託管記憶體,建立一個自動增加託管記憶體的執行緒,增加非託管記憶體。
輸入d,釋放主執行緒建立的託管記憶體物件。

using System;
using System.Collections.Generic;
using System.Text;

namespace TestMemorySize
{
    class Program
    {
        static void Main(string[] args)
        {
            MemoryInc memoryInc = new MemoryInc();

            while (true)
            {
                long memorysize = System.Diagnostics.Process.GetCurrentProcess().PagedMemorySize64;

                Console.WriteLine(string.Format("PagedMemorySize:{0}MB", memorysize / (1024*1024)));
                Console.WriteLine(string.Format("ManagedMemIncTimes:{0}", memoryInc.ManagedMemIncTimes));
                Console.WriteLine(string.Format("UnmanagedMemIncTimes:{0}", memoryInc.UnmanagedMemIncTimes));

                String cmd = Console.ReadLine();

                switch (cmd)
                {
                    case "d":
                        memoryInc = new MemoryInc();
                        GC.Collect();
                        break;
                    case "m":
                        memoryInc.IncManagedMemory();
                        break;
                    case "u":
                        memoryInc.IncUnmanagedMemory();
                        break;
                    case "t":
                        MemoryLeakThread thread = new MemoryLeakThread();
                        break;
                    case "l":
                        break;
                }
            }
        }
    }
}

 

MemoryInc 是一個增加託管記憶體和非託管記憶體的類。

 

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace TestMemorySize
{
    class MemoryInc
    {
        int _ManagedMemIncTimes = 0;
        int _UnmanagedMemIncTimes = 0;

        List<byte[]> _ManagedMemory = new List<byte[]>();
        LinkedList<IntPtr> _UnmanagedMemory = new LinkedList<IntPtr>();

        /// <summary>
        /// Managed memory increase times
        /// </summary>
        public int ManagedMemIncTimes
        {
            get
            {
                return _ManagedMemIncTimes;
            }
        }

        /// <summary>
        /// Unmanaged memory increase times
        /// </summary>
        public int UnmanagedMemIncTimes
        {
            get
            {
                return _UnmanagedMemIncTimes;
            }
        }

        /// <summary>
        /// Increase managed memory
        /// </summary>
        public void IncManagedMemory()
        {
            _ManagedMemIncTimes++;

            _ManagedMemory.Add(new byte[1024 * 1024 * _ManagedMemIncTimes]);
        }

        /// <summary>
        /// Increase unmanaged memory
        /// </summary>
        public void IncUnmanagedMemory()
        {
            _UnmanagedMemIncTimes++;

            _UnmanagedMemory.AddLast(Marshal.AllocCoTaskMem(1024 * 1024 * _UnmanagedMemIncTimes));
        }
    }
}

 

MemoryLeakThread 這個執行緒沒30秒增加1M的託管記憶體佔用。

 

 


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace TestMemorySize
{
    class MemoryLeakThread
    {
        Thread _Thread;
        byte[] _Buf;
        int _Times = 0;

        private void ThreadProc()
        {
            while (true)
            {
                _Times++;
                _Buf = new byte[_Times * 1024 * 1024];
                Thread.Sleep(30 * 1000);
            }
        }

        public MemoryLeakThread()
        {
            _Thread = new Thread(new ThreadStart(ThreadProc));
            _Thread.IsBackground = true;
            _Thread.Start();
        }
    }
}


     準備就緒,下面就開始體驗了。

1、託管記憶體的跟蹤
       選單中選擇Profiler->Start 啟動TestMemorySize.exe,然後輸入m 並回車,這是分配了1M的託管記憶體。
在選單中選擇Profiler->Collect Heap Shapshot. 這是就可以看到堆中的所有物件了。

 

 

從這個介面我們看到雖然列出了物件的列表,但只有型別和大小等資訊,卻沒有物件的名稱以及分配過程
資訊,這樣怎麼定位那塊記憶體沒有被釋放啊?不要著急,.NET Memory Profiler還是比較強大的,讓我們繼續往下
前進。

 

雙擊選中的物件後進入物件所佔用的堆的詳細資訊

 

 

再雙擊選中行,這時我們就可以看到物件的名稱和分配堆疊的情況了。是不是很興奮?終於找到是哪個傢伙在搗蛋了。

 

 

2、執行緒中建立的託管記憶體的跟蹤
       執行緒中建立的託管記憶體跟蹤方法和第1節介紹的方法基本是一樣的。啟動TestMemorySize.exe後輸入t 並回車,建立一個
吃記憶體的執行緒。下面步驟都相同了。

 

 

 

3、非託管記憶體的跟蹤
       要跟蹤非託管記憶體需要做一個設定:選擇選單中view->Project Property Pages,按下圖進行設定。

 

設定好後啟動TestMemorySize.exe後輸入u 並回車,建立1M的非託管記憶體。下面步驟相同。

 

 

 

 

非託管記憶體無法看到物件的名稱,但可以看到記憶體的申請過程,這對於定位記憶體問題已經提供了很大的幫助。

現在我們再輸入m 回車,建立1M的託管記憶體,然後輸入d 回車,這時我們可以發現memoryInc物件申請的託管記憶體已經被釋放掉,
但非託管記憶體依然存在,記憶體在這裡洩漏了!

這個工具還可以幫助我們計算出託管物件在堆中實際佔用的記憶體大小,這也是一個很實用的功能,我們可以發現實際的佔用大小
要比我們設計的大小略大,這是因為我們設計的類及其成員都是從一些基類中繼承,這些基類的資料佔用了一些記憶體造成。

 

到此如何跟蹤基本.net應用的記憶體問題就介紹完畢。有時間再謝謝怎麼跟蹤ASP.NET應用的記憶體問題。
這一篇本來上午就要發出來,都快寫完了,IE 崩潰!抓狂!

下午又重新寫了一遍,鬱悶啊。