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

비밀번호

댓글 작성자
 




... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13122정성태8/26/20227416.NET Framework: 2045. C# 11 - 메서드 매개 변수에 대한 nameof 지원
13121정성태8/23/20225426C/C++: 157. Golang - 구조체의 slice 필드를 Reflection을 이용해 변경하는 방법
13120정성태8/19/20226882Windows: 209. Windows NT Service에서 UI를 다루는 방법 [3]
13119정성태8/18/20226428.NET Framework: 2044. .NET Core/5+ 프로젝트에서 참조 DLL이 보관된 공통 디렉터리를 지정하는 방법
13118정성태8/18/20225353.NET Framework: 2043. WPF Color의 기본 색 영역은 (sRGB가 아닌) scRGB [2]
13117정성태8/17/20227447.NET Framework: 2042. C# 11 - 파일 범위 내에서 유효한 타입 정의 (File-local types)파일 다운로드1
13116정성태8/4/20227912.NET Framework: 2041. C# - Socket.Close 시 Socket.Receive 메서드에서 예외가 발생하는 문제파일 다운로드1
13115정성태8/3/20228279.NET Framework: 2040. C# - ValueTask와 Task의 성능 비교 [1]파일 다운로드1
13114정성태8/2/20228410.NET Framework: 2039. C# - Task와 비교해 본 ValueTask 사용법파일 다운로드1
13113정성태7/31/20227649.NET Framework: 2038. C# 11 - Span 타입에 대한 패턴 매칭 (Pattern matching on ReadOnlySpan<char>)
13112정성태7/30/20228073.NET Framework: 2037. C# 11 - 목록 패턴(List patterns) [1]파일 다운로드1
13111정성태7/29/20227891.NET Framework: 2036. C# 11 - IntPtr/UIntPtr과 nint/nuint의 통합파일 다운로드1
13110정성태7/27/20227930.NET Framework: 2035. C# 11 - 새로운 연산자 ">>>" (Unsigned Right Shift)파일 다운로드1
13109정성태7/27/20229256VS.NET IDE: 177. 비주얼 스튜디오 2022를 이용한 (소스 코드가 없는) 닷넷 모듈 디버깅 - "외부 원본(External Sources)" [1]
13108정성태7/26/20227339Linux: 53. container에 실행 중인 Golang 프로세스를 디버깅하는 방법 [1]
13107정성태7/25/20226544Linux: 52. Debian/Ubuntu 계열의 docker container에서 자주 설치하게 되는 명령어
13106정성태7/24/20226171오류 유형: 819. 닷넷 6 프로젝트의 "Conditional compilation symbols" 기본값 오류
13105정성태7/23/20227471.NET Framework: 2034. .NET Core/5+ 환경에서 (프로젝트가 아닌) C# 코드 파일을 입력으로 컴파일하는 방법 - 두 번째 이야기 [1]
13104정성태7/23/202210539Linux: 51. WSL - init에서 systemd로 전환하는 방법
13103정성태7/22/20227120오류 유형: 818. WSL - systemd-genie와 관련한 2가지(systemd-remount-fs.service, multipathd.socket) 에러
13102정성태7/19/20226538.NET Framework: 2033. .NET Core/5+에서는 구할 수 없는 HttpRuntime.AppDomainAppId
13101정성태7/15/202215376도서: 시작하세요! C# 10 프로그래밍
13100정성태7/15/20227924.NET Framework: 2032. C# 11 - shift 연산자 재정의에 대한 제약 완화 (Relaxing Shift Operator)
13099정성태7/14/20227782.NET Framework: 2031. C# 11 - 사용자 정의 checked 연산자파일 다운로드1
13098정성태7/13/20226054개발 환경 구성: 647. Azure - scale-out 상태의 App Service에서 특정 인스턴스에 요청을 보내는 방법 [1]
13097정성태7/12/20225460오류 유형: 817. Golang - binary.Read: invalid type int32
... 16  17  18  19  [20]  21  22  23  24  25  26  27  28  29  30  ...