.NET 的 Process 類別中設計有關 Memory 的屬性運用來監測應用程式的記憶體用量

透過 .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 工作管理員顯示的記憶體使用數字。
PrivateMemorySize64800 MB程式跟 OS 請求會用到的記憶體數字。若是當下沒使用到記憶體,也會在 OS 被記錄與保留。
PagedMemorySize64800 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 ~).
 

MVP_Logo



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" 軟體製作。