一:背景 在 記憶體泄漏 的系列問題中,有一類問題是 記憶體碎片化 導致的,而且這種更容易發生在 LOH 上,因為它預設不開啟 對象壓縮,一般遇到這種情況,優先讓朋友執行下麵的代碼應急。 GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHe ...
一:背景
在 記憶體泄漏
的系列問題中,有一類問題是 記憶體碎片化
導致的,而且這種更容易發生在 LOH 上,因為它預設不開啟 對象壓縮
,一般遇到這種情況,優先讓朋友執行下麵的代碼應急。
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
後續再研究問題根源,這篇我們就來聊一聊如何用 PerfView
神器幫助我們尋找 記憶體碎片化
的根源。
二:碎片化洞察
1. WinDbg 的局限
為了方便講述,先上一段造成 LOH記憶體碎片化
的測試代碼。
internal class Program
{
static void Main(string[] args)
{
Test();
Console.ReadLine();
}
public static List<byte[]> list = new List<byte[]>();
static void Test()
{
for (int i = 0; i < 50000; i++)
{
if (i % 2 == 0)
{
list.Add(new byte[85000 * 2]);
list[i] = null;
}
else
{
list.Add(new byte[85000]);
}
}
Console.WriteLine("5w 數據插入完畢!");
}
}
代碼邏輯非常簡單,就是間隔釋放其中的 byte[]
對象,讓 Free 和 Live 對象成交錯狀, 可以用 WinDbg 觀察如下:
0:009> !dumpheap
Address MT Size
...
00000000f0cd6028 000000000050ff60 170088 Free
00000000f0cff890 00007ffdb4a25490 85024
00000000f0d144b0 000000000050ff60 170088 Free
00000000f0d3dd18 00007ffdb4a25490 85024
00000000f0d52938 000000000050ff60 170088 Free
00000000f0d7c1a0 00007ffdb4a25490 85024
00000000f0d90dc0 000000000050ff60 170088 Free
00000000f0dba628 00007ffdb4a25490 85024
00000000f0dcf248 000000000050ff60 170088 Free
00000000f0df8ab0 00007ffdb4a25490 85024
00000000f0e0d6d0 000000000050ff60 170088 Free
00000000f0e36f38 00007ffdb4a25490 85024
00000000f0e4bb58 000000000050ff60 170088 Free
00000000f0e753c0 00007ffdb4a25490 85024
00000000f0e89fe0 000000000050ff60 170088 Free
00000000f0eb3848 00007ffdb4a25490 85024
00000000f0ec8468 000000000050ff60 170088 Free
00000000f0ef1cd0 00007ffdb4a25490 85024
00000000f0f068f0 000000000050ff60 170088 Free
00000000f0f30158 00007ffdb4a25490 85024
00000000f0f44d78 000000000050ff60 170088 Free
...
雖然用 WinDBG 可以輕鬆找出,但這裡有一個非常大的局限,就是你不知道 Free 對象生前是什麼東西,往往這時候就只能用 db,dc,du 看記憶體地址,在無計可施的情況下, PerfView 就可以大顯威龍了。
2. PerfView 洞察
接下來我們打開PerfView,採用預設設置啟動收集,稍等之後,點擊 Memory -> GCStats
項,觀察 LOH Frag %
列,如下圖所示:
從圖中的 LOH Frag %
列可以看出碎片化確實蠻高的,接下來我們就是找 Free 塊生前是什麼東西,如果能記錄到 Free
生成是由誰分配的那該有多好呀!!!
哈哈,在 PerfView 中還真有這麼一個視圖叫 Gen 2 Object Deaths Stacks
,如下圖所示:
從名字上就能看到,這個視圖記錄的是 LOH 上那些已經死亡對象的生前 Stack,當然了,這是按權重計算的,如果是 Event 模式產生的就好了,那會記錄所有的對象分配。
接下來雙擊 Gen 2 Object Deaths Stacks
再選中我們的應用程式,可以看到權重占比最高的是 System.Byte[]
對象,如下圖所示:
接下來右鍵點擊 Goto -> Goto Item in Callers
按鈕,可以看到占比最高的是 Program.Test()
分配所致,高達 9396 個,如下圖所示:
接下來就是調研 Program.Test()
方法,找出最後被 Free 的原因, 這就是 PerfView 和 WinDbg 雙劍合璧的威力。