Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 

C# - 윈도우 작업 관리자와 리소스 모니터의 메모리 값을 구하는 방법

다음의 그림을 보고 시작할까요? ^^

windows_memory_1.png

위의 수치를 보면서 작업 관리자와 리소스 모니터의 용어를 정리해 보겠습니다.

작업 관리자의 "MEMORY" 32GB == 리소스 모니터의 "Installed" 32,768MB
작업 관리자의 "Memory usage" 31.9GB == 리소스 모니터의 "Total" 32,683MB

작업 관리자의 "In use" 27.0GB == 리소스 모니터의 "In Use" 27,604MB
작업 관리자의 "Available" 4.9GB == 리소스 모니터의 "Available" == "Standby + Free" 4,977MB

작업 관리자의 "Cached" 4.9GB == 리소스 모니터의 "Cached" == "Modified + Standby" 4,938MB
작업 관리자의 "Hardware reserved" 84.5MB == 리소스 모니터의 "Hardware Reserved" 85MB

좀 더 자세한 설명은, 리소스 모니터의 용어에 마우스를 올려 보면 나오는 다음의 툴팁을 참고할 수 있습니다.

  • Hardware Reserved: Memory that is reserved for use by the BIOS and some drivers for other peripherals
  • In Use: Memory used by processes, drivers, or the operating system
  • Modified: Memory whoose contents must be written to disk before it can be used for another purpose
  • Standby: Memory that contains cached data and code that is not actively in use
  • Free: Memory that does not contain any valuable data, and that will be used first when processes, drivers or the operating system need more memory

  • Available: Amount of memory (including Standby and Free memory) that is immediately available for use by processes, drivers, or the operating system
  • Cached: Amount of memory (including Standby and Modified memory) containing cached data and code for rapid access by processes, drivers, or the operating system
  • Total: Amount of physical memory available to the operating system, device drivers, and processes
  • Installed: Amount of physical memory installed in the computer

자, 그럼 이제 쉽게 정리해 볼까요? ^^

우선, 여러분이 구매한 PC의 물리 메모리로 장착한 용량을 작업 관리자의 "Memory" 또는 리소스 모니터의 "Installed"를 통해서 알 수 있습니다. 하지만 대개의 경우 그 물리 메모리를 전부 쓸 수 있는 것은 아니고 컴퓨터의 장치 요소들 중에는 메모리를 요구하는 것이 있어 그런 것들에 할당되는 메모리는 사실상 응용 프로그램에서 사용할 수 없으므로 제외하게 됩니다. 그 메모리가 바로 작업 관리자와 리소스 모니터의 "Hardware reserved"입니다.

따라서 제 컴퓨터는 총 32,768MB의 RAM이 설치되어 있는데 그중 85MB를 제외한 32,683MB의 메모리만 쓸 수 있는 것입니다.




이제 32,683MB 메모리에서, 실질적으로 운영체제 및 프로그램이 사용하는 메모리를 빼야 합니다. 그것이 바로 "In Use"로 말 그대로 "사용 중"인 메모리입니다.

"사용 중"이긴 하지만, 동일한 프로그램을 올려 놓은 상황에서 만약 메모리가 (32GB가 아닌) 16GB라면 "In Use" 메모리가 줄어들 수도 있습니다. 왜냐하면 저것은 프로그램의 "Working Set"이기 때문에 메모리가 충분하면 더 올려놓고, 부족하면 시기적절하게 페이징 해버리기 때문입니다. 따라서 동일한 프로그램인데도 컴퓨터의 사양에 따라 다르게 나올 수 있으므로 특별한 상황이 아니라면 그렇게 다르다는 사실에 크게 주목하지 않아도 됩니다.

"In Use"를 제외하면 이제 Modified, Standby, Free 영역이 남습니다.

이 중에서 Modified와 Standby는 시스템의 성능을 높이기 위한 목적의 Cache 용도로 운영체제가 일부러, 나쁘게 말하면 ^^ 요행을 바라며 쓸데없이 사용 중인 메모리입니다. 하지만 Standby가 읽기 용도로 올려진 cache인 반면, 그중에서 운이 좋아 "쓰기"까지 진행된 영역이 "Modified"입니다. 이 2가지는 같은 cache 용도였지만 해당 메모리를 해제하는 시점의 처리 방식이 달라진다는 차이점이 있습니다. 즉, Standby는 별다른 조치 없이 바로 해제할 수 있지만 Modified는 그전에 반드시 디스크에 기록해야 하므로 부하가 발생하는 것입니다.

따라서, 운영체제는 특정 프로그램이 메모리를 요구했을 때 "Standby" 영역을 아무런 부하 없이 응용 프로그램에게 할당할 수 있기 때문에 "Standby"와 "Free" 영역을 합쳐 가용(Available) 메모리라고 하는 것입니다. 결국 리소스 모니터에서 "Free"가 39MB만 남았다고 해서 자신의 시스템이 현재 심각한 RAM 부족 현상이 발생하는 것은 아니라는 것을 의미합니다.




자, 그럼 "리소스 모니터"가 보여주는 메모리를 Win32 API를 이용해 프로그램으로 구해볼까요? ^^ 우선 시스템에 설치된 RAM 용량을 GetPhysicallyInstalledSystemMemory API로 가져올 수 있습니다.

GetPhysicallyInstalledSystemMemory function
; https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getphysicallyinstalledsystemmemory

public class SafeNativeMethods
{
    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetPhysicallyInstalledSystemMemory(out ulong MemoryInKilobytes);
}

SafeNativeMethods.GetPhysicallyInstalledSystemMemory(out ulong physicalMemory);

그다음 "Hardware Reserved" 용량은 어떻게 구해야 할까요? 이 값은 리소스 모니터의 "Available" 메모리를 가져와 위에서 구한 physicalMemory에서 빼면 구할 수 있습니다. 이를 위해 GetPerformanceInfo API로부터,

GetPerformanceInfo function
; https://learn.microsoft.com/en-us/windows/desktop/api/psapi/nf-psapi-getperformanceinfo

Total 메모리(즉, Available)를 구하고,

[DllImport("psapi.dll", SetLastError = true)]
public static extern bool GetPerformanceInfo(out PERFORMANCE_INFORMATION pPerformanceInformation, uint cb);

PERFORMANCE_INFORMATION pi = new PERFORMANCE_INFORMATION();
pi.cb = (uint)Marshal.SizeOf(pi);
SafeNativeMethods.GetPerformanceInfo(out pi, pi.cb);

ulong pageSize = pi.PageSize.ToUInt64();
ulong physicalTotal = pi.PhysicalTotal.ToUInt64(); // == Available 메모리 용량

Console.WriteLine($"PhysicalTotal == \t{physicalTotal * pageSize / 1024 / 1024} MB");

이 값을 통해 "Hardware Reserved" 크기를 유추할 수 있습니다.

double reserved = (physicalMemory - (pi.Total / 1024.0));
long reservedMB = (long)Math.Round(reserved / 1024.0);
Console.WriteLine($"Reserved == \t\t\t{reservedMB} MB");




가용 메모리(Standby + Free)를 구할 수 있는 또 다른 API가 있습니다.

GlobalMemoryStatusEx function
; https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex

MEMORYSTATUSEX structure
; https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/ns-sysinfoapi-_memorystatusex

ullAvailPhys: The amount of physical memory currently available, in bytes. This is the amount of physical memory that can be immediately reused without having to write its contents to disk first. It is the sum of the size of the standby, free, and zero lists.


[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX buffer);

MEMORYSTATUSEX globalMemoryStatus = new MEMORYSTATUSEX();
SafeNativeMethods.GlobalMemoryStatusEx(ref globalMemoryStatus);

Console.WriteLine($"MEMORYSTATUSEX.ullAvailPhys: {globalMemoryStatus.ullAvailPhys / 1024.0 / 1024.0} MB");

재미있는 것은 가용 메모리는 구해도 Standby와 Free를 개별적으로 구할 수는 없다는 점입니다. (아래에서 성능 카운터를 이용해 구하는 방법이 나옵니다.)

Standby의 경우 GetPerformanceInfo의 PERFORMANCE_INFORMATION.SystemCache로 근사치를 구할 수 있는데, 애석하게도 이 값은 리소스 모니터에서 나온 "Standby"에 비해서는 항상 작게 나옵니다.

마지막으로 구할 수 있는 수치가 "In Use + Modified"입니다. 이것은 (Installed가 아닌) 전체 메모리에서 Available 메모리를 빼면 됩니다.

ulong inuse_modified = (ulong) ((pi.Total - pi.Available) / 1024.0);
Console.WriteLine($"Total - Available - HardwareReserved: {inuse_modified / 1024.0:#,#.0} MB ({inuse_modified / 1024.0 / 1024.0:#,#.0} GB)");

마찬가지로 (성능 카운터의 도움을 받지 않고) API 수준에서 "In Use"와 "Modified"를 개별적으로 구할 수 있는 방법은 없습니다. (혹시, 아시는 분은 덧글 부탁드립니다. ^^)




Win32 API가 아닌, Performance Counter로 눈을 돌리면 이제 "Modified" 값을 구할 수 있습니다.

\Memory\Modified Page List Bytes 

이 값을 구할 수 있으니, 위에서 구했던 "inuse_modified" 값에서 빼면 "In Use" 용량도 구할 수 있습니다. 또한 다음의 성능 카운터는,

\Memory\Free & Zero Page List Bytes

리소스 관리자의 "Free" 영역의 수치이므로 역시 이전에 구했던 가용 메모리에서 빼면 "Standby" 용량도 구할 수 있습니다. 그럼, 전부 구했군요. ^^ 다음의 소스 코드는 위에서 설명한 내용을 바탕으로 리소스 모니터와 작업 관리자에서 보여주는 수치를 구합니다.

using System;
using System.Diagnostics;
using System.Threading;

class Program
{
    private static PerformanceCounter freeMemory;
    private static PerformanceCounter modifiedMemory;

    static void Main(string[] args)
    {
        freeMemory = new PerformanceCounter("Memory", "Free & Zero Page List Bytes", true);
        modifiedMemory = new PerformanceCounter("Memory", "Modified Page List Bytes", true);

        while (true)
        {
            PERFORMANCE_INFORMATION pi = new PERFORMANCE_INFORMATION();
            pi.Initialize();
            SafeNativeMethods.GetPerformanceInfo(out pi, pi.cb);

            Console.WriteLine("[Resource Monitor]");

            SafeNativeMethods.GetPhysicallyInstalledSystemMemory(out ulong installedMemory);

            double reserved = (installedMemory - (pi.Total / 1024.0));
            ulong modified = (ulong)modifiedMemory.RawValue;
            ulong inuse = pi.Total - pi.Available - modified;

            long reservedMB = (long)Math.Round(reserved / 1024.0);
            Console.WriteLine($"Hardware Reserved: {reservedMB} MB");

            Console.WriteLine($"In Use: {inuse / 1024 / 1024} MB");
            Console.WriteLine($"Modified: {modified / 1024 / 1024} MB");

            ulong free = (ulong)freeMemory.RawValue;
            ulong standby = pi.Available - free;
            Console.WriteLine($"Standby: {standby / 1024 / 1024} MB");
            Console.WriteLine($"Free: {free / 1024 / 1024} MB");
            Console.WriteLine();
            Console.WriteLine($"Available: {pi.Available.MB()} MB");
            Console.WriteLine($"Cached: {(standby + modified).MB()} MB");
            Console.WriteLine($"Total: {pi.Total.MB()} MB");
            Console.WriteLine($"Installed: {installedMemory / 1024} MB");

            MEMORYSTATUSEX globalMemoryStatus = new MEMORYSTATUSEX();
            globalMemoryStatus.Initialize();
            SafeNativeMethods.GlobalMemoryStatusEx(ref globalMemoryStatus);

            Console.WriteLine();
            Console.WriteLine("[Task Manager]");

            Console.WriteLine($"Memory: {installedMemory / 1024.0 / 1024.0} GB");
            Console.WriteLine($"Memory usage: {pi.Total / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine();
            Console.WriteLine($"In use: {inuse / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Available: {pi.Available / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Committed: {pi.Commit / 1024.0 / 1024.0 / 1024.0:#.0} / {globalMemoryStatus.ullTotalPageFile / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Cached: {(standby + modified) / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Paged pool: {pi.KernelPage / 1024.0 / 1024.0 / 1024.0:#.0} GB");
            Console.WriteLine($"Non-paged pool: {pi.KernelNonPage / 1024.0 / 1024.0:#} GB");

            Console.WriteLine();
            Thread.Sleep(1000);
        }
    }
}

그리고 다음은 이렇게 구한 수치와 실제 리소스 모니터/작업 관리자의 수치를 비교하는 모습입니다.

windows_memory_2.png

서로 구하는 시간 차가 약간 있는 것을 감안하면 대충 맞는 걸로 보입니다. ^^

(이 글의 예제 프로젝트는 github에 있습니다.)




그 외에, 메모리 사용량을 알아낼 수 있는 방법들을 검색해 보면 위에서 설명한 범위 내에서 구해지는 것들입니다. 예를 들어, WMI의 FreePhysicalMemory, TotalVisibleMemorySize 등을 사용할 수 있다고 나오는데,

wmic OS get FreePhysicalMemory, TotalVisibleMemorySize

실제로 구해지는 값을 보면, FreePhysicalMemory는 Available 용량을, TotalVisibleMemorySize는 물리 메모리에서 "Hardware Reserved" 용량을 뺀 값(단위: KB)에 해당합니다. 그 외에 TotalPhysicalMemory도 있는데,

wmic computersystem get TotalPhysicalMemory

마찬가지로 TotalVisibleMemorySize 값(단위: 바이트)과 같습니다.




기타 다음의 글들도 읽어보시면 도움이 될 것입니다.

작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미
; https://www.sysnet.pe.kr/2/0/1850

The usable memory may be less than the installed memory on Windows 7-based computers
; https://support.microsoft.com/en-gb/help/978610/the-usable-memory-may-be-less-than-the-installed-memory-on-windows-7-b




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 3/9/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...
NoWriterDateCnt.TitleFile(s)
13296정성태3/25/20233691Windows: 234. IsDialogMessage와 협업하는 WM_GETDLGCODE Win32 메시지 [1]파일 다운로드1
13295정성태3/24/20233954Windows: 233. Win32 - modeless 대화창을 modal처럼 동작하게 만드는 방법파일 다운로드1
13294정성태3/22/20234125.NET Framework: 2105. LargeAddressAware 옵션이 적용된 닷넷 32비트 프로세스의 가용 메모리 - 두 번째
13293정성태3/22/20234192오류 유형: 853. dumpbin - warning LNK4048: Invalid format file; ignored
13292정성태3/21/20234312Windows: 232. C/C++ - 일반 창에도 사용 가능한 IsDialogMessage파일 다운로드1
13291정성태3/20/20234716.NET Framework: 2104. C# Windows Forms - WndProc 재정의와 IMessageFilter 사용 시의 차이점
13290정성태3/19/20234222.NET Framework: 2103. C# - 윈도우에서 기본 제공하는 FindText 대화창 사용법파일 다운로드1
13289정성태3/18/20233418Windows: 231. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 자식 윈도우를 생성하는 방법파일 다운로드1
13288정성태3/17/20233516Windows: 230. Win32 - 대화창의 DLU 단위를 pixel로 변경하는 방법파일 다운로드1
13287정성태3/16/20233686Windows: 229. Win32 - 대화창 템플릿의 2진 리소스를 읽어들여 윈도우를 직접 띄우는 방법파일 다운로드1
13286정성태3/15/20234147Windows: 228. Win32 - 리소스에 포함된 대화창 Template의 2진 코드 해석 방법
13285정성태3/14/20233738Windows: 227. Win32 C/C++ - Dialog Procedure를 재정의하는 방법파일 다운로드1
13284정성태3/13/20233938Windows: 226. Win32 C/C++ - Dialog에서 값을 반환하는 방법파일 다운로드1
13283정성태3/12/20233480오류 유형: 852. 파이썬 - TypeError: coercing to Unicode: need string or buffer, NoneType found
13282정성태3/12/20233815Linux: 58. WSL - nohup 옵션이 필요한 경우
13281정성태3/12/20233720Windows: 225. 윈도우 바탕화면의 아이콘들이 넓게 퍼지는 경우 [2]
13280정성태3/9/20234462개발 환경 구성: 670. WSL 2에서 호스팅 중인 TCP 서버를 외부에서 접근하는 방법
13279정성태3/9/20234005오류 유형: 851. 파이썬 ModuleNotFoundError: No module named '_cffi_backend'
13278정성태3/8/20233961개발 환경 구성: 669. WSL 2의 (init이 아닌) systemd 지원 [1]
13277정성태3/6/20234583개발 환경 구성: 668. 코드 사인용 인증서 신청 및 적용 방법(예: Digicert)
13276정성태3/5/20234313.NET Framework: 2102. C# 11 - ref struct/ref field를 위해 새롭게 도입된 scoped 예약어
13275정성태3/3/20234665.NET Framework: 2101. C# 11의 ref 필드 설명
13274정성태3/2/20234254.NET Framework: 2100. C# - ref 필드로 ref struct 타입을 허용하지 않는 이유
13273정성태2/28/20233955.NET Framework: 2099. C# - 관리 포인터로서의 ref 예약어 의미
13272정성태2/27/20234211오류 유형: 850. SSMS - mdf 파일을 Attach 시킬 때 Operating system error 5: "5(Access is denied.)" 에러
13271정성태2/25/20234145오류 유형: 849. Sql Server Configuration Manager가 시작 메뉴에 없는 경우
1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...