一:背景 這篇我們來聊一下 PerfView 在協助 WinDbg 分析 Dump 過程中的兩個超實用技巧,可能會幫助我們快速定位最後的問題,主要有如下兩塊: 洞察記憶體泄漏中的靜態大集合變數名。 驗證當前程式的 GC 模式。 這裡就把經驗分享一下,希望讓大家少走彎路。 二:如何洞察 1. 查看靜態變 ...
一:背景
這篇我們來聊一下 PerfView 在協助 WinDbg 分析 Dump 過程中的兩個超實用技巧,可能會幫助我們快速定位最後的問題,主要有如下兩塊:
-
洞察記憶體泄漏中的靜態大集合變數名。
-
驗證當前程式的 GC 模式。
這裡就把經驗分享一下,希望讓大家少走彎路。
二:如何洞察
1. 查看靜態變數名
如果有過 dump 分析經驗的朋友應該知道,當你歷經千辛萬苦在 記憶體泄漏
的dump文件中找到了那個記憶體泄漏最大的集合,但遺憾的是,你不知道這個 集合 的變數名叫什麼?
為了方便講述,先上一段測試代碼:
namespace ConsoleApp10
{
internal class Program
{
static void Main(string[] args)
{
Task.Run(Alloc1);
Console.ReadLine();
}
public static List<string> mybiglist = new List<string>();
static void Alloc1()
{
var rand = new Random();
for (int i = 0; i < 10000; i++)
{
mybiglist.Add(string.Join(",", Enumerable.Range(1, 1000)));
Console.WriteLine(mybiglist.Count);
}
}
}
}
接下來把程式跑起來,終於你找到了那個記憶體占用最大的 List<string>
集合,代碼如下:
0:000> !gcroot -all 0000000002e27038
HandleTable:
00000000004A13E8 (strong handle)
-> 000000001A841018 System.Object[]
-> 000000000284D680 System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]]
-> 0000000012841038 System.String[]
-> 0000000002E27038 System.String
可以看到,這個變數被 HandleTable
所持有,從經驗上來說其實就是一個 static 變數,現在我們迫切需要知道這個變數名叫什麼,因為離真相真的咫尺之遙了。。。
如果你沒有彙編基礎,我敢打賭你肯定在 WinDBG 中找不到這個變數名。 那有沒有快捷的方式顯示變數名呢? 肯定是可以的,這就需要藉助 PerfView 。
接下來點擊菜單的 Memory -> Take Heap Snapshot From Dump
按鈕,彈出如下對話框,輸入 dump 文件以及 output 地址,截圖如下:
接下來點擊 Dump GC Heap
讓 PerfView 從 ConsoleApp10.dmp
中採樣生成 *.gcdump 文件,接下來點擊 Heap Stacks -> RefTree
,通過 Inc%
可以觀察到 [static vars]
下的 mybiglist
採樣占比最大,如圖所示:
到這裡第一個問題也就解決了,原來是一個叫 mybiglist
的 List<string>
集合把記憶體給吃掉了,是不是非常的方便哈。
2. 查看手工修改的 GC 模式
在我的 dump 分析之旅中,曾經就遇到過一個案例,需要修改 GC 模式,比如說 併發模式
改成 非併發模式
,那改完之後我如何驗證呢?
第一種方式就是通過 x
命令去搜 coreclr 中的符號,比如下麵這樣:
0:000> x coreclr!GCConfig*
00007ffa`782763f6 coreclr!GCConfig::s_ConcurrentGC = true
00007ffa`7827b799 coreclr!GCConfig::s_ServerGC = false
雖然可以用 WinDbg 實現,但這種需要生成 dump 或者附加到進程中,那能不能在沒有侵入的情況下獲取 CoreCLR 當前的 GC 模式呢? 肯定是可以的,這又得需要藉助 PerfView 啦, 它的底層邏輯是截獲 Runtime/Start
這個 ETW 事件,在這個事件中有一個叫 StartupFlags
枚舉,裡面就記錄著當前的 GC 模式。
為了方便講述,在 *.csproj
中修改 GC 的模式為 Server 版,代碼如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Platforms>AnyCPU;x86</Platforms>
</PropertyGroup>
</Project>
接下來啟動 PerfView ,點擊 Collect -> Collect
啟動收集,然後把程式跑起來,停止收集後,我們在 Filter 中輸入 Runtime/Start
事件,如果你的列表中沒有 StartupFlags
列的話,記得在 Cols
上選擇一下哦,截圖如下:
從圖中可以看到,當前的 StartupFlags=8392707
,那這一串數字代表什麼意思呢?這就需要到 CoreCLR 中找到它的枚舉定義,接下來我們寫段代碼將它翻譯出字元串形式。
internal class Program
{
static void Main(string[] args)
{
var value = "8392707";
Enum.TryParse<Test>(value, out var result);
var txt = result.ToString().Replace(", ", "\r\n");
Console.WriteLine(txt);
}
[Flags]
enum Test
{
STARTUP_CONCURRENT_GC = 0x1,
STARTUP_LOADER_OPTIMIZATION_MASK = (0x3 << 1),
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN = (0x1 << 1),
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN = (0x2 << 1),
STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN_HOST = (0x3 << 1),
STARTUP_LOADER_SAFEMODE = 0x10,
STARTUP_LOADER_SETPREFERENCE = 0x100,
STARTUP_SERVER_GC = 0x1000,
STARTUP_HOARD_GC_VM = 0x2000,
STARTUP_SINGLE_VERSION_HOSTING_INTERFACE = 0x4000,
STARTUP_LEGACY_IMPERSONATION = 0x10000,
STARTUP_DISABLE_COMMITTHREADSTACK = 0x20000,
STARTUP_ALWAYSFLOW_IMPERSONATION = 0x40000,
STARTUP_TRIM_GC_COMMIT = 0x80000,
STARTUP_ETW = 0x100000,
STARTUP_ARM = 0x400000,
STARTUP_SINGLE_APPDOMAIN = 0x800000,
STARTUP_APPX_APP_MODEL = 0x1000000,
STARTUP_DISABLE_RANDOMIZED_STRING_HASHING = 0x2000000
}
}
程式跑起來後,截圖如下:
從圖中可以清晰的看到,當前的 GC 模式為 CONCURRENT_GC & SERVER_GC
,這和 WinDBG 的輸出不約而同。
好了,本篇就聊這兩個超實用的分析技巧,希望對大家有所幫助。