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

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  [53]  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12324정성태9/11/202010822개발 환경 구성: 514. smigdeploy.exe를 이용한 Windows Server 2016에서 2019로 마이그레이션 방법
12323정성태9/11/20209728오류 유형: 649. Copy Database Wizard - The job failed. Check the event log on the destination server for details.
12322정성태9/11/202010939개발 환경 구성: 513. Azure VM의 RDP 접속 위치 제한 [1]
12321정성태9/11/20208996오류 유형: 648. netsh http add urlacl - Error: 183 Cannot create a file when that file already exists.
12320정성태9/11/202010156개발 환경 구성: 512. RDP(원격 데스크톱) 접속 시 비밀 번호를 한 번 더 입력해야 하는 경우
12319정성태9/10/20209952오류 유형: 647. smigdeploy.exe를 Windows Server 2016에서 실행할 때 .NET Framework 미설치 오류 발생
12318정성태9/9/20209385오류 유형: 646. OpenVPN - "TAP-Windows Adapter V9" 어댑터의 "Network cable unplugged" 현상
12317정성태9/9/202011728개발 환경 구성: 511. Beats용 Kibana 기본 대시 보드 구성 방법
12316정성태9/8/202010131디버깅 기술: 170. WinDbg Preview 버전부터 닷넷 코어 3.0 이후의 메모리 덤프에 대해 sos.dll 자동 로드
12315정성태9/7/202012450개발 환경 구성: 510. Logstash - FileBeat을 이용한 IIS 로그 처리 [2]
12314정성태9/7/202011085오류 유형: 645. IIS HTTPERR - Timer_MinBytesPerSecond, Timer_ConnectionIdle 로그
12313정성태9/6/202012181개발 환경 구성: 509. Logstash - 사용자 정의 grok 패턴 추가를 이용한 IIS 로그 처리
12312정성태9/5/202016050개발 환경 구성: 508. Logstash 기본 사용법 [2]
12311정성태9/4/202011302.NET Framework: 937. C# - 간단하게 만들어 보는 리눅스의 nc(netcat), json_pp 프로그램 [1]
12310정성태9/3/202010576오류 유형: 644. Windows could not start the Elasticsearch 7.9.0 (elasticsearch-service-x64) service on Local Computer.
12309정성태9/3/202010320개발 환경 구성: 507. Elasticsearch 6.6부터 기본 추가된 한글 형태소 분석기 노리(nori) 사용법
12308정성태9/2/202011599개발 환경 구성: 506. Windows - 단일 머신에서 단일 바이너리로 여러 개의 ElasticSearch 노드를 실행하는 방법
12307정성태9/2/202012335오류 유형: 643. curl - json_parse_exception / Invalid UTF-8 start byte
12306정성태9/1/202010478오류 유형: 642. SQL Server 시작 오류 - error code 10013
12305정성태9/1/202011403Windows: 172. "Administered port exclusions"이 아닌 포트 범위 항목을 삭제하는 방법
12304정성태8/31/202010334개발 환경 구성: 505. 윈도우 - (네트워크 어댑터의 우선순위로 인한) 열거되는 IP 주소 순서를 조정하는 방법
12303정성태8/30/202010536개발 환경 구성: 504. ETW - 닷넷 프레임워크 기반의 응용 프로그램을 위한 명령행 도구 etrace 소개
12302정성태8/30/202010408.NET Framework: 936. C# - ETW 관련 Win32 API 사용 예제 코드 (5) - Private Logger파일 다운로드1
12301정성태8/30/202010737오류 유형: 641. error MSB4044: The "Fody.WeavingTask" task was not given a value for the required parameter "IntermediateDir".
12300정성태8/29/202010112.NET Framework: 935. C# - ETW 관련 Win32 API 사용 예제 코드 (4) CLR ETW Consumer파일 다운로드1
12299정성태8/27/202011042.NET Framework: 934. C# - ETW 관련 Win32 API 사용 예제 코드 (3) ETW Consumer 구현파일 다운로드1
... 46  47  48  49  50  51  52  [53]  54  55  56  57  58  59  60  ...