1 簡介
.Net Memory Profiler(以下簡稱Profiler):專門針對於.NET程序,功能最全的內存分析工具,最大的特點是具有內存動態分析(Automatic Memory Analysis)功能。
2 安裝
安裝程序為SciTech.NET.Memory.Profiler.v4.0.114. 安裝+注冊機
下載地址:http://download.csdn.net/detail/wmlunge/4972685
安裝完成后直接覆蓋安裝目錄下的 memprofilerstandalone.dll 、netmemprofilerbase.dll 和 netmemprofilerconsole.exe,然后雙擊license.reg 文件即可完成注冊。
3 使用方法
Profler可以調試4種類型的.NET程序,分別為:
l 桌面應用程序
l WPF程序
l ASP.NET程序
l .NET Service程序
對應選擇軟件的文件菜單如下
Profler調試共有三種方式選擇:
l 啟動跟蹤(Profiler Application)
選定對應的調試方式,如調試桌面程序,選中Profiler Application,然后選擇需要啟動的執行文件,Profiler將作為宿主程序啟動程序開始實時監控內存.
l 附加進程(Attach Process)
將Profiler附加到指定的進程上,此時不能實時監控內存情況,只能夠收集內存鏡像.
l 導入內存鏡像(Import Memory Dump)
可以選擇dmp為后綴的內存鏡像文件,比如Windbg以及DebugDiag導出的鏡像文件,此時不能實時監控內存情況,只能夠收集內存鏡像且不能跟蹤非托管資源.
3.1 軟件設置
為了加快Profiler分析內存類型實例的速度,需要設置程序的符號路徑即(Symbol File Locations),進入菜單Tool->Options->Preferences->Symobl File Locations,得到彈出菜單如下圖.
選中”Retrive Debug Symbols ..”選項,該選項是為了將被調試程序需要的PDB符號文件從Http://msdl.microsoft.com/download/symbols下載下來.並選定一個目錄緩存原來下過的符號路徑,如果有其他的分目錄存放路徑,則指定”Additional Symbols file locations”選項.
注:如果選擇了從微軟網站下載符號會影響調試程序的啟動時間,建議使用本地符號集緩存
3.2 操作說明
3.2.1 啟動程序
首先,選擇需要調試類型,調試ZLBH桌面程序,選擇 Profiler Application,選擇好需要啟動的程序exe文件.
如果需要設置啟動參數,則設置好命令行參數以及工作目錄.
選擇”Next”進行收集數據的一些選項設置,一般直接按”Star”按鈕開始調試程序.
3.2.2 收集數據
選擇菜單欄的收集按鈕,收集堆數據,第一個為收集全部堆上的數據,第二個為只收集第0代的數據.
3.2.3 重新啟動和停止
調試完畢后通過停止按鈕跟蹤程序,通過啟動按鈕重新啟動上一次的調試程序.
-啟動
-停止
3.2.4 查看收集數據
Profiler上有6個頁卡,分別為:
l Type/Resource 類型/資源頁卡
l Type/Resource Details類型/資源明細頁卡
l Instance Details 實例明細頁卡
l Call Stacks/Methods調用堆棧頁卡
l Navtive Memory 本地內存頁卡
l Real-Time-實時跟蹤頁卡
3.2.4.1 Type/Resource 類型/資源頁卡
類型/資源頁卡,可以看到當前收集的內存快照的實例數/實例字節數等信息.
通過類型/資源網格的上部可以過濾出需要的信息,共有四個地方可以過濾,從左到右分別為:
l 資源類型
托管資源
非托管資源
l 警告類型
Profiler自動分析的內存問題警告類型
l 命名空間
類型的命名空間
l 類型名稱
按輸入過濾類型名稱
類型的過濾還可以通過,”Show type/Resource”下拉框過濾出所有的已有類型.“Show hierarcical”通過命名空間分類顯示類型和資源.
Live Instances 列顯示當前活動的實例數
Total:總共建立的實例數
New:新建的實例數
Remved:已經銷毀的實例數
Delta:New –Removed,新建和銷毀數的差值.
Comparison SnapShop:另一個用來比較的內存快照來比較兩個快照的差別
3.2.4.2 Type/Resource Details類型/資源明細頁卡
通過在Type/Resource視圖中選中某個類型則顯示類型資源的明細信息,包括該類型下所有的類型實例.
左側包括的信息包括:
l 是否新建的實例
l 實例號
l 被引用的次數
l 實例所占用的內存大小
l 實例的代信息
l 實例的子級對象所占用的內存大小
右側包含Allocation Stacks和Shortest Root Paths,如果不是實時跟蹤,則沒有Allocation Stacks頁卡.
l Allocation Stacks顯示的是Win32調用路徑
l Shortest Root Paths 顯示的是從根對象到當前實例的引用路徑,查看順序從下往上,為根到實例的路徑.
3.2.4.3 Instance Details 實例明細頁卡
通過點擊Type/Resource Details類型/資源明細頁卡上的單個實例,顯示這個實例的明細信息,顯示的主要內容包括:
l Referenced By 被引用的關系
l References 引用的關系
l Field Value 屬性的值
3.2.4.4 Call Stacks/Methods調用堆棧頁卡
顯示調用及方法堆棧,可以選擇只包含托管代碼和非托管代碼
顯示方法所調用的函數及被調用的函數關系,如圖:
3.2.4.5 Navtive Memory 本地內存頁卡
用於顯示進程的本地內存信息,本地內存是被操作系統管理的內存,而不是CLR管理的內存。
3.2.4.6 Real-Time-實時跟蹤頁卡
如果通過Profiler Application調試程序,則能夠顯示出Real-Time頁卡,主要內容有:
l Graph and Statistics
通過圖形顯示實時的內存分配情況,包括:總共實例數、存貨實例數、Disposed實例數等
l Type/Resources
實時的顯示出類型和資源信息,並顯示最后一次gc存活的實例數以及總共的實例數。
3.2.5 自動內存分析
.NET Memory Profiler分析工具能夠根據內存鏡像以及實時跟蹤進行自動內存問題分析,提供6個嚴重級別的提示,分別為嚴重警告、警告、輕度警告、間接警告、建議、提示。
其中對應的嚴重級別又會有不同的原因分類提示:
嚴重警告 |
|||
Potential Memory Leak |
潛在的內存泄漏 |
|
|
Disposed instance with direct EventHandler roots |
實例已Disposed但有直接的EventHandler根 |
一個Disposed的實例直接被一個EventHandler根化,這個實例只能通過代理訪問到 |
|
Disposed instance with direct delegate roots |
實例已Disposed但有直接的Delegate根 |
|
|
Undisposed instances (release resource, no finalizer) |
沒有被Disposed的實例 |
一個Disposable實例被GC回收,但是因為沒有finalzier方法而沒有正確的Dispose,從未導致外部的非托管資源沒有被釋放掉 |
|
警告 |
|||
Direct EventHandler roots |
被一個EventHandler直接根化 |
一個實例直接被一個EventHandler根化,需要檢查這個實例以及這個EventHandler實例,是否實例被EventHandler把持 |
|
Disposed instance |
被Disposed的實例 |
一個實例雖然被Disposed但是還是標記為可到達(Reachable),需要進一步檢查該實例是否是活動的(alive) |
|
Undisposed instances (release resource and remove external references) |
沒有被Disposed的實例 |
一個實例被GC回收,但是沒有dispose,Disposable類型的實例由於沒有Dispose,導致非托管資源以及外部引用不能被移除 |
|
Undisposed instances (release resource) |
沒有被Disposed的實例 |
||
Undisposed instances (remove external references) |
沒有被Disposed的實例 |
||
輕度警告 |
|||
Direct delegate roots |
直接被代理所根化 |
一個Disposed的實例直接被一個Delegate根化,這個實例只能通過代理訪問到 |
|
Pinned instance |
被釘住的實例 |
釘在內存中的對象因為實例不能移動,會影響GC回收效率 |
|
間接警告 |
|||
Disposed instance with indirect EventHandler roots |
已Disposed的對象被EventHandler間接根化 |
|
|
Indirect EventHandler roots |
非直接被EventHandler所根化 |
|
|
Disposed instance with indirect delegate roots |
已Disposed的對象被Deletegate間接根化 |
|
|
Indirect delegate roots |
非直接被代理所根化 |
|
|
建議 |
|||
Undisposed instances (perform action) |
沒有被Disposed的實例 |
實例被回收,但是沒有正確的Dispose,實例在Dispose的過程中,需要執行一些Exit或Clearup操作,包括:寫數據到文件、提交或回滾事務、清除緩存、刪除臨時文件等 |
|
Undisposed instances (memory/resource utilization) |
實例在Dispose的過程中需要Dispose其他實例,比如:釋放COM接口、suppress finalization |
||
提示 |
|||
Large instance |
大型實例對象 |
需要存放到大對象堆的實例 |
|
Undisposed instances (clear references) |
Dispose實例需要清空其他實例的引用的操作,但是沒有執行 |
||
Undisposed instances (no action) |
|
||
Undisposed instances (unclassified) |
|||
4 常見內存問題
4.1 使用了非托管資源的類
非托管資源的類是指本身是被CLR管理的,而且其管理的非托管資源也可以被CLR自動回收,因為CLR只能跟蹤非托管資源的生存期,但是不能主動去做GC,所以GC的時機不確定,所以在使用完后應及時釋放。
例如:調用FileStream
FileStream file = new FileStream(@"c:\Test.txt", FileMode.Open);
連續兩次調用程序會報“文件正在使用中”的異常,如果兩次調用中間調用強制回收,則不會報異常。
再例如:使用ODP.NET的OracleCommand和OracleDataReader,在Close后還需要Dispose;
OracleCommand cmd = new OracleCommand();
cmd.CommandText = sbSQL.ToString();
cmd.Connection = conn;
cmd.Parameters.Add(p1);
OracleDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{
//…
dr.Close();
}
else
{
}
dr.Dispose();
cmd.Dispose();
常見的使用了非托管資源的類如下:
ApplicationContext |
Component |
ComponentDesigner |
Brush |
Container |
Context |
Cursor |
FileStream |
DataSet |
Font |
Icon |
Image |
Matrix |
Texture |
OdbcDataReader |
OleDBDataReader |
Pen |
Regex |
Socket |
StreamWriter |
Timer |
Transaction |
DataReader |
Ping |
Tooltip |
Bitmap |
SerialPort |
以上列出的類均繼承了IDisposable接口,需要在使用完后調用Dispose方法釋放或者使用Using語句塊,比如DataTable、DataSet、DataReader、Transaction、BitMap…
4.2 Win32API及COM
指通過本地API函數與托管對象進行交互(比如:通過 P/Invoke方式調用本地DLL,DLLImport聲明靜態外部函數和COM Interop)所用到的非托管資源。
例如:當通過DLL Import調用 API函數GetDC函數時忘了調用ReleaseDC去釋放設備句柄造成4個字節的內存泄漏。
再如:智能文檔中使用的Word以及導出EXCEl功能用到的Office的COM非托管組件,在關閉時GC不能識別COM組件而造成有時候無法對COM對象進行釋放,這時候可以通過以下兩個InteropServices函數進行釋放
l System.Runtime.InteropServices.Marshal.ReleaseComObject(comObject);
遞減與指定的 COM 對象關聯的指定 運行時可調用包裝 (RCW) 的引用計數。
返回值為關聯的 RCW 的引用計數的新值。此值通常為零,因為無論調用包裝COM 對象的托管客戶端有多少,RCW 僅保留對該對象的一次引用。
所以通過這個方法顯式的通過CLR釋放非托管 COM 對象上的所有引用。
l System.Runtime.InteropServices.Marshal.FinalReleaseComObject(comObject);
通過將 運行時可調用包裝 (RCW) 的引用計數設置為 0,釋放對它的所有引用。
返回值為與 comObject參數關聯的 RCW 的引用計數的新值,如果釋放成功,則為 0(零)。
4.3 事件造成的內存泄漏
l 當不需要使用事件時,應退訂事件,為了確保安全可以在Dispose方法中退訂事件.
l 當對象不再觸發事件時,應該將對象設為null來移除所有的事件訂閱者
4.4 動態添加生成控件造成內存泄漏
動態生成引用了非托管資源的控件后,注意一定要Dispose();
例如:
RichTextBox rtb = new RichTextBox();
frm.Controls.Add(rtb);
frm.Controls.Remove(rtb);
rtb.Dispose();
下載地址: SciTech.NET.Memory.Profiler.v4.0.114. 安裝+注冊機
安裝完成后直接覆蓋安裝目錄下的 memprofilerstandalone.dll 、netmemprofilerbase.dll 和 netmemprofilerconsole.exe,然后雙擊license.reg 文件即可完成注冊。