用 .NET Memory Profiler 跟蹤.net 應(yīng)用內(nèi)存使用情況--基本應(yīng)用篇
Posted on 2008-09-05 15:15 eaglet 閱讀(32851) 評論(24) 收藏 舉報用 .NET Memory Profiler 跟蹤.net 應(yīng)用內(nèi)存使用情況--基本應(yīng)用篇
作者:肖波
.net 框架號稱永遠(yuǎn)不會發(fā)生內(nèi)存泄漏,原因是其引入了內(nèi)存回收的機(jī)制。但實際應(yīng)用中,往往我們分配了對象但沒有釋放指向該對象的引用,導(dǎo)致對象永遠(yuǎn)無法釋放。最常見的情況就是給對象添加了事件處理函數(shù),但當(dāng)不再使用該對象時卻沒有將該函數(shù)從對象的事件handler中減掉。另外如果分配了非托管內(nèi)存,而沒有手工釋放,GC同樣無能為力。所以當(dāng).net應(yīng)用發(fā)生內(nèi)存泄漏后如何跟蹤應(yīng)用的內(nèi)存使用情況,定位到程序設(shè)計中的缺陷顯得非常重要。本文將介紹通過.NET Memory Profiler來跟蹤.net應(yīng)用的內(nèi)存泄漏,為定位.net應(yīng)用內(nèi)存問題提供一個解決途徑。
.NET Memory Profiler是一款強(qiáng)大的.net 內(nèi)存跟蹤和優(yōu)化工具。該工具目前可以對一下4種.net應(yīng)用進(jìn)行內(nèi)存跟蹤。
- 基本應(yīng)用 例如winform, console application等
- ASP.net 應(yīng)用
- WPF應(yīng)用
- Window 服務(wù)
本篇將通過對以下三種內(nèi)存的跟蹤來闡述如何使用該工具對基本.net應(yīng)用程序進(jìn)行內(nèi)存的跟蹤。三種內(nèi)存包括:
- 托管內(nèi)存
- 線程托管內(nèi)存
- 非托管內(nèi)存
在開始之前,先需要建立環(huán)境。
我采用.NET Memory Profiler V3.1.307 版本進(jìn)行測試。安裝完后需要新建一個項目,由于我們需要測
.net基本應(yīng)用,所以新建項目時選擇Standalone application. 點擊next后,輸入要測試的.net 應(yīng)用的路徑和參數(shù)。
然后按下 finish.項目就建立完成了。
測試程序是我編寫的,編譯后生成TestMemorySize.exe 這個控制臺應(yīng)用程序。下載地址
代碼如下
主程序等待用戶輸入,輸入m,t,u 分別是增加托管內(nèi)存,創(chuàng)建一個自動增加托管內(nèi)存的線程,增加非托管內(nèi)存。
輸入d,釋放主線程創(chuàng)建的托管內(nèi)存對象。
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 是一個增加托管內(nèi)存和非托管內(nèi)存的類。
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的托管內(nèi)存占用。
準(zhǔn)備就緒,下面就開始體驗了。
1、托管內(nèi)存的跟蹤
菜單中選擇Profiler->Start 啟動TestMemorySize.exe,然后輸入m 并回車,這是分配了1M的托管內(nèi)存。
在菜單中選擇Profiler->Collect Heap Shapshot. 這是就可以看到堆中的所有對象了。
從這個界面我們看到雖然列出了對象的列表,但只有類型和大小等信息,卻沒有對象的名稱以及分配過程
信息,這樣怎么定位那塊內(nèi)存沒有被釋放啊?不要著急,.NET Memory Profiler還是比較強(qiáng)大的,讓我們繼續(xù)往下
前進(jìn)。
雙擊選中的對象后進(jìn)入對象所占用的堆的詳細(xì)信息
再雙擊選中行,這時我們就可以看到對象的名稱和分配堆棧的情況了。是不是很興奮?終于找到是哪個家伙在搗蛋了。
2、線程中創(chuàng)建的托管內(nèi)存的跟蹤
線程中創(chuàng)建的托管內(nèi)存跟蹤方法和第1節(jié)介紹的方法基本是一樣的。啟動TestMemorySize.exe后輸入t 并回車,創(chuàng)建一個
吃內(nèi)存的線程。下面步驟都相同了。
3、非托管內(nèi)存的跟蹤
要跟蹤非托管內(nèi)存需要做一個設(shè)置:選擇菜單中view->Project Property Pages,按下圖進(jìn)行設(shè)置。
設(shè)置好后啟動TestMemorySize.exe后輸入u 并回車,創(chuàng)建1M的非托管內(nèi)存。下面步驟相同。
非托管內(nèi)存無法看到對象的名稱,但可以看到內(nèi)存的申請過程,這對于定位內(nèi)存問題已經(jīng)提供了很大的幫助。
現(xiàn)在我們再輸入m 回車,創(chuàng)建1M的托管內(nèi)存,然后輸入d 回車,這時我們可以發(fā)現(xiàn)memoryInc對象申請的托管內(nèi)存已經(jīng)被釋放掉,
但非托管內(nèi)存依然存在,內(nèi)存在這里泄漏了!
這個工具還可以幫助我們計算出托管對象在堆中實際占用的內(nèi)存大小,這也是一個很實用的功能,我們可以發(fā)現(xiàn)實際的占用大小
要比我們設(shè)計的大小略大,這是因為我們設(shè)計的類及其成員都是從一些基類中繼承,這些基類的數(shù)據(jù)占用了一些內(nèi)存造成。
到此如何跟蹤基本.net應(yīng)用的內(nèi)存問題就介紹完畢。有時間再謝謝怎么跟蹤ASP.NET應(yīng)用的內(nèi)存問題。
這一篇本來上午就要發(fā)出來,都快寫完了,IE 崩潰!抓狂!
下午又重新寫了一遍,郁悶啊。
浙公網(wǎng)安備 33010602011771號