透過 .NET API 的 Process 所提供記憶體資訊的屬性運用,可以自我監測 .NET 應用程式佔據記憶體的狀況。
以下列舉三個屬性來介紹:
| 屬性 | 意義 |
| PagedMemorySize64 | 可被分頁到磁碟的記憶體數字 |
| PrivateMemorySize64 | 程式跟系統請求使用的專用記憶體數字(不會跟其他行程共用的部分) |
| WorkingSet64 | 實際駐留在 RAM 的記憶體數字 |

情境舉例:
假設 .NET 程式執行過程中向 Windows 要求了 800 MB 的記憶體陣列 (ex: new byte[800*1024*1024]),但目前卻只有使用了其中的 350 MB。而電腦的實體記憶體被作業系統與其他程式使用後,整體所剩下的 RAM 也很充足的情況下(若不足,作業系統會依據需要進行分頁處理)。
上述的 .NET API 中 Process 的屬性紀錄的數值可能發生以下的呈現結果:
| 屬性 | 數值 (預估) | 意義 |
| WorkingSet64 | ~350 MB | 佔用電腦的實體記憶體數字 ,會接近 Windows 工作管理員顯示的記憶體使用數字。 |
| PrivateMemorySize64 | 800 MB | 程式跟 OS 請求會用到的記憶體數字。若是當下沒使用到記憶體,也會在 OS 被記錄與保留。 |
| PagedMemorySize64 | 800 MB | 同上。此數字代表會需要分頁檔支援的記憶體。 |
如果要長期監看 .NET 應用程式記憶體用量的狀況,使用 .NET API 的 Process 中 WorkingSet64 作為衡量數字,會是比較合理的選擇。
也更能更貼近應用程式在 Windows 系統中所運作時,實際佔用記憶體的資訊。
實際撰寫一段 .NET 的 C# 程式:
using System.Diagnostics;
var processes = Process.GetProcessesByName("notepad"); // 以記事本為例
foreach (var proc in processes)
{
// 轉換為 MB (1 MB = 1024 * 1024 bytes)
double memoryInMB = proc.WorkingSet64 / 1024.0 / 1024.0;
Console.WriteLine($"Process: {proc.ProcessName} (ID: {proc.Id})");
Console.WriteLine($"記憶體使用量 (Working Set): {memoryInMB:F2} MB");
}當然,若要更精確的取得記憶體用量,在 Windows 上可以使用 PerformanceCounter 來處理。
備註:
如果要抓 Memory Leak: 則可以透過 PrivateMemorySize64的使用來監測變化。
要取得應用程式在 Linux 系統中的佔用記憶體的數據,則可透過 Linux 計算 Process 佔用多少記憶體 的類似方式取得;或是透過 Linux 當中直接讀取 /proc/[pid]/statm 的方式會更為可靠。
若透過讀取 /proc/[pid]/statm 的方式,要完成 .NET 的 C# 程式則如以下範例:
using System.Diagnostics;
using System.IO;
public static double GetTotalVmRssMB(string processName)
{
long totalBytes = 0;
foreach (var p in Process.GetProcessesByName(processName))
{
try
{
string statmPath = $"/proc/{p.Id}/statm";
if (!File.Exists(statmPath))
continue;
var contents = File.ReadAllText(statmPath).Trim();
var parts = contents.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 2)
continue;
long rssPages = long.Parse(parts[1]);
long pageSize = Environment.SystemPageSize;
totalBytes += rssPages * pageSize;
}
catch
{
// process may exit
}
}
// convert to MB
return totalBytes / 1024.0 / 1024.0;
}
當然 .NET API 的 Process 類別當中所定義的 WorkingSet64 屬性,仍是可以使用的。
再搭配一點點 Linq 語法就可以一行搞定:
Process.GetProcessesByName("NetworkManager").Sum(p => p.WorkingSet64)帶入 "應用程式名稱" (上述例子為 "NetwrokManager"),直接取得想要監測的 Process(es),再加總其 WorkingSet64 的屬性值資料即可。
進一步看看 .NET API 在透過 Process 類別的 WorkingSet64 屬性在 Linux 的實作上,就是如上述的 GetTotalVmRssMB方法的類似設計:https://github.com/dotnet/runtime/blob/main/src/libraries/System.Diagnostics.Process/src/System/Diagnostics/ProcessManager.Linux.cs
如果將 Windows 跟 Linux 的記憶體使用設計,對照來看 .NET API 的 Process 類別當中的 WorkingSet64 屬性的話,就會如下圖的對照概念:

Windows 上透過呼叫 Windows 原生 API;Linux 透過讀取 /proc/[pid]/statm 的方式。
作業系統的原理概念雷同對照起來是大同小異的,但最底層的實作方式仍會不同。
I'm a Microsoft MVP - Developer Technologies (From 2015 ~).

I focus on the following topics: Xamarin Technology, Azure, Mobile DevOps, and Microsoft EM+S.
If you want to know more about them, welcome to my website:
https://jamestsai.tw
本部落格文章之圖片相關後製處理皆透過 Techsmith 公司 所贊助其授權使用之 "Snagit" 與 "Snagit Editor" 軟體製作。