1. 程式人生 > 其它 >VS自帶的診斷工具

VS自帶的診斷工具

前言

一般來說.NET程式設計師是不用擔心記憶體分配釋放問題的,因為有垃圾收集器(GC)會自動幫你處理。但是GC只能收集那些不再使用的記憶體(根據物件是否被其它活動的物件所引用)來確定。所以如果程式碼編寫不當的話,仍然會出現記憶體洩漏的問題,常見的情況有:一個靜態變數引用了一個應該被釋放的物件,事件註冊後不解除註冊,非託管資源使用後沒有手動釋放。不斷的記憶體洩漏終會引起記憶體不足,掛掉你的程式。

對於這種記憶體洩漏問題,有很多的分析工具可以使用,常見的有CLRProfiler、ANTS Performance Profiler等。不過從vs2013起,vs自帶了一個分析工具-Diagnostic Tool,預設debug時會自動開啟,如果沒有開啟的話就按快捷鍵Ctrl+Alt+F2。

開始使用

這裡以靜態變數持有應釋放的物件為例,簡單介紹下使用方法。
這裡有個winform程式,主要功能即使點選按鈕後,輸出名為“jim”的個人資訊:

private void button1_Click(object sender, EventArgs e)
{
     var p = PersonManager.Get("jim");
     label1.Text += p.Name + " " + p.Age + "\n";
}

Person和PersonManager類的資訊如下:

namespace MemLeak
{
    public class Person
    {
        
public string Name { get; set; } public int Age { get; set; } public byte[] BinaryData { get; set; } } public static class PersonManager { static List<Person> people = new List<Person>(); public static List<Person> People {
get { return people; } } public static Person Get(string name) { //正常邏輯 //var per = People.FirstOrDefault(o => o.Name == name); //if(per==null) //{ // per = new Person(); // per.Age = 23; // per.Name = name; // per.BinaryData = File.ReadAllBytes(@"D:\2.zip");//一個38MB的檔案 // people.Add(per); //} //return per; //錯誤邏輯 Person p = new Person(); p.Age = 23; p.Name = name; p.BinaryData = File.ReadAllBytes(@"D:\2.zip");//一個38MB的檔案 people.Add(p); return p; } } }
View Code

1. 捕獲記憶體快照

程式執行起來後,從下圖可以看到記憶體佔用大約在18M左右,此時點選“擷取快照”新建一個快照:

然後click按鈕5次,然後再點選 截圖快照 ,如下:

從曲線圖中可以明顯看到記憶體從18M逐步增長到227M。第二次快照與第一次相比物件增加了19個,堆上的記憶體使用增加了191M左右。

點選圖中的(+19)字樣,可以進入到快照詳細對比介面。

2. 詳細對比

如圖所示,我們先來看下選中的那一行是什麼意思:

可以看到MemLeak.Person這個物件的計數差異為+5,計數是5。表示這兩次快照之間這個Person物件從0個變成了5個增加了5個。大小差異是增加了195601040個位元組,這幾個從字面意思看都很好理解。唯一有點難度的可能是非獨佔大小(位元組),一眼看不出來是幹什麼的。但是我們注意到了List<MemLeak.Person>物件的大小是68位元組而非獨佔大小卻是195601108位元組。
非獨佔大小的統計方式如下:

其中最下層的stringbyte[],由於沒有子節點了,所以它們的非獨佔大小就是它們本身的大小。Address的非獨佔大小=自己本身的大小+String的非獨佔大小(50位元組)+Byte[]的非獨佔大小(100位元組)=200位元組。Customer的非獨佔大小=自己本身(100位元組)+String的非獨佔大小(100位元組)+Address的非獨佔大小(200位元組)=400位元組。
所以這也就解釋了List<MemLeak.Person>,自身大小僅是68位元組,但是由於它是一個List內部包含了5個Person物件,所以它的非獨佔大小就是5個Person物件大小的和。

3. 哪裡產生了洩漏?

這裡我先將過濾器裡“僅我的程式碼”和“摺疊小型物件型別”前面的勾去掉,便於分析。

因為我們值需要看當前程式集(MemLeak)的記憶體資訊,所以在搜尋型別名的框裡直接輸入我們程式集的名稱,將其他一些沒用的過濾掉。

是不是清爽了很多!
分析記憶體當然要拿佔用記憶體增多最大的物件開刀,點選MemLeak.Person,在下面的“根的路徑”檢視中可以看到增加的5個Person物件是在一個Person[]陣列中。

但是我們好像並沒有宣告過這樣的陣列,所以繼續展開:


原來是在一個List裡面,而且這個List還是靜態的。這時候就要想到記憶體洩漏的原因,可能就是程式碼哪裡的靜態變數持續佔有應該釋放的物件了。然後更改為正確程式碼就OK了。

4. 檢視物件中哪種資料型別佔用記憶體較多?

切換到的引用選項卡(“引用的型別”)。

可以看到本例中PersonByte[]型別的佔用記憶體較多,所以我們就可以重點排查Person中哪裡用到了Byte[]型別,進而去優化。

5、當初取消勾選的那兩個選項是什麼?

第一個是“僅我的程式碼”:
勾上之後,會過濾掉那些.Net Runtime產生的一些物件和一些很常見的系統認為與你的程式無關的一些物件。

第二個是:“摺疊小的物件型別”:
勾上後,會過隱藏掉那些非獨佔大小小於託管堆總大小0.5%的物件。將這些小物件的非獨佔大小合併的父節點中。如下:

這兩個選項預設都勾上即可。

轉載:https://blog.csdn.net/catshitone/article/details/79002823