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.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.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 崩潰!抓狂!
下午又重新寫了一遍,郁悶啊。