Windows의 「메모리 사용량」은 무엇을 나타내는가 ── Working Set·Private Bytes·Commit·페이지 파일을 올바르게 읽는 법
· 업데이트: · Go Komura · Windows, Windows 개발, 메모리 관리, Working Set, Private Bytes, Commit, 페이지 파일, 성능 모니터링, 장애 조사, Sysinternals
수정 이력(1건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- 한국어 전면 재작성에 맞춰 본문 표현을 바로잡았습니다. 기술적인 주장은 일본어판과 같습니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22175974)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「Windows의 「메모리 사용량」은 무엇을 나타내는가 ── Working Set·Private Bytes·Commit·페이지 파일을 올바르게 읽는 법」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/windows-memory-usage-working-set-commit/
- DOI(등록된 아카이브)
- 10.5281/zenodo.22175974
- DOI(마지막 등록 버전)
- 10.5281/zenodo.22175975
작업 관리자에서는 어떤 프로세스의 「메모리」가 1.2GB로 표시됩니다. 그런데 Process Explorer에서는 Working Set이 1.5GB, Private Bytes가 2.4GB이고, VMMap에서는 Size가 그보다 더 큽니다. 시스템 전체를 보면 「커밋됨 19.6/31.8GB」로 나옵니다.
그렇다면 이 앱은 결국 몇 GB의 메모리를 쓰고 있는 것일까요?
답은 무엇을 알고 싶은지에 따라 봐야 할 숫자가 달라진다는 것입니다. 지금 RAM에 올라와 있는 양을 알고 싶은지, 그 프로세스 고유의 할당량을 알고 싶은지, 시스템이 앞으로도 지원하겠다고 약속한 양을 알고 싶은지, 단순히 가상 주소를 잡아 둔 범위를 알고 싶은지에 따라 볼 지표가 달라집니다.
Windows 메모리 지표가 어려운 이유는, 모두 「메모리」라는 같은 말로 표시되지만 실제로는 다음처럼 서로 다른 축을 재고 있기 때문입니다.
- 주소 공간을 얼마나 쓰고 있는가
- 커밋(Commit)을 얼마나 소비하고 있는가
- 현재 물리 RAM에 상주하고 있는가
- 그 페이지가 프로세스 고유인지, 공유 가능한지
- 시스템 전체에서 앞으로 얼마나 더 할당을 감당할 수 있는가
이 글에서는 Windows 10/11 및 현행 Windows Server에서 앱의 메모리 증가나 시스템 전체의 메모리 부족을 조사하는 사람을 대상으로, Working Set, Private Working Set, Private Bytes, Commit, Virtual Bytes, 페이지 파일, Available, 페이지 폴트의 관계를 한 장으로 이어서 설명합니다.
.NET 객체가 회수되지 않는 원인을 추적하는 절차는 「.NET에서 GC 대기와 메모리 누수를 가려내기」, VMMap이나 Process Explorer의 구체적인 조작은 「Process Explorer / Handle / VMMap 실전」에서 자세히 다룹니다. 이 글은 그 전제가 되는 Windows OS 쪽 숫자를 읽는 법에 집중합니다.
1. 먼저 결론
- Working Set은 현재 RAM에 상주하고 있는 페이지입니다. 프로세스 고유 페이지뿐 아니라, DLL 코드나 메모리 매핑 파일처럼 다른 프로세스와 공유할 수 있는 페이지도 포함합니다.1
- Private Working Set은 Working Set 가운데 지금 그 프로세스에만 속하는 부분입니다. 「지금 이 프로세스가 고유하게 차지하고 있는 RAM」을 보는 근사값으로 쓸 수 있지만, 앱이 확보한 전체 양은 아닙니다.2
- Private Bytes는 그 프로세스 고유의 커밋 양입니다. 현재 RAM에 올라와 있는지는 별개의 지표입니다. Win32 API 구조체의
PagefileUsage도 현재 Windows에서는 실질적으로 같은 Commit Charge를 나타내며, 페이지 파일에 실제로 기록된 바이트 수가 아닙니다.2 - 작업 관리자의 「커밋됨 X/Y」에서 X는 시스템 전체의 현재 커밋 양, Y는 커밋 상한입니다. X는 페이지 파일 사용량이 아닙니다. Y는 대체로 RAM과 페이지 파일의 합계로 결정됩니다.3
- Reserve와 Commit은 다릅니다. 가상 주소를 Reserve만 했다면 그 범위를 나중에 쓰려고 잡아 둔 것일 뿐이며, RAM도 커밋 상한도 그만큼 소비하지 않습니다.45
- 페이지 폴트는 반드시 디스크 I/O를 뜻하지는 않습니다. RAM 안에서 끝나는 소프트 폴트와, 페이지 파일·실행 파일·메모리 매핑 파일 등에서 읽어 오는 하드 폴트가 있습니다.16
- 메모리 누수는 한 번의 값이 아니라, 같은 부하를 반복했을 때의 기울기로 판단합니다. 특히 Private Bytes나 그 내역이 처리가 끝난 뒤에도 단계적으로 계속 늘고, 같은 정상 상태로 돌아가지 않는지를 봅니다.
한 문장으로 정리하면, Working Set은 「지금 RAM에 있는 양」, Private Bytes는 「이 프로세스 고유로 약속된 양」, Commit은 「시스템 전체가 약속한 양」입니다.
flowchart TB
accTitle: Windows의 주요 메모리 지표 고르기
accDescr: 알고 싶은 대상이 RAM 상주량, 프로세스 고유 커밋량, 시스템 전체 커밋량, 가상 주소 범위 중 무엇인지에 따라 봐야 할 지표가 달라진다
question["「메모리 사용량」으로 무엇을 알고 싶은가"]
question -->|지금 RAM에 있는 양| workingSet["Working Set"]
question -->|프로세스 고유의 약속량| privateBytes["Private Bytes"]
question -->|시스템 전체의 약속량| systemCommit["System Commit"]
question -->|잡아 둔 주소 범위| virtualBytes["Virtual Bytes / Reserved"]
workingSet --> resident["물리 RAM에 상주"]
privateBytes --> privateCommit["프로세스 고유 Commit"]
systemCommit --> commitLimit["Commit Limit과 비교"]
virtualBytes --> addressSpace["가상 주소 공간"]
그림 1: 「메모리가 많다」는 관찰을 먼저 네 가지 질문으로 나눕니다.
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 18건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. 「메모리 사용량」을 네 가지 축으로 나누기
먼저 Windows 메모리를 「하나의 막대」가 아니라 네 가지 축으로 생각합니다.
flowchart TB
accTitle: 페이지 하나를 분류하는 네 가지 독립 축
accDescr: 가상 주소 상태, 커밋된 페이지의 뒷받침, 물리 RAM 상주 여부, 다른 프로세스와의 공유 가능성을 각각 따로 확인한다
page["페이지 하나를 네 가지 축으로 보기"]
page --> address["주소 상태"]
address --> addressValues["Free / Reserved / Committed"]
page --> backing["뒷받침"]
backing --> backingValues["Page-file-backed / File-backed"]
page --> residentAxis["RAM 상주"]
residentAxis --> residentValues["Resident / Not resident"]
page --> sharing["공유 가능성"]
sharing --> sharingValues["Private / Shareable"]
그림 2: 페이지 하나라도 주소 상태·뒷받침·상주·공유성은 각각 따로 결정됩니다.
Mapped는 Free·Reserved·Committed와 나란한 주소 상태가 아니라 영역의 종류입니다. 매핑된 뷰의 페이지도 Committed가 될 수 있습니다. 또한 Private는 뒷받침 매체가 아니라 공유 가능성의 분류입니다. 그래서 뒷받침은 Page-file-backed인지 File-backed인지, 공유 가능성은 Private인지 Shareable인지를 따로 읽습니다.
이 네 축을 조합하면 대표적인 지표의 관계는 다음과 같습니다.
| 페이지 상태 | Working Set | Private Working Set | Private Bytes | Virtual Bytes 계열 |
|---|---|---|---|---|
| 프로세스 고유·커밋됨·RAM 상주 | 포함 | 포함 | 포함 | 포함 |
| 프로세스 고유·커밋됨·RAM 비상주 | 미포함 | 미포함 | 포함 | 포함 |
| DLL이나 매핑 파일의 공유 페이지·RAM 상주 | 포함 | 원칙적으로 미포함 | 원칙적으로 미포함 | 포함 |
| 예약되었지만 아직 커밋되지 않음 | 미포함 | 미포함 | 미포함 | 포함될 수 있음 |
| 사용하지 않는 주소 범위 | 미포함 | 미포함 | 미포함 | 보통 미포함 |
flowchart TB
accTitle: 페이지 종류와 주요 메모리 지표의 대응
accDescr: Private로 상주하는 페이지, Private로 비상주인 페이지, 공유로 상주하는 페이지, 예약만 된 범위가 어느 지표에 포함되는지 보여준다
privateResident["Private·Commit됨·RAM 상주"]
privateNonresident["Private·Commit됨·RAM 비상주"]
sharedResident["공유 페이지·RAM 상주"]
reservedOnly["Reserved·미Commit"]
workingSet["Working Set"]
privateWorkingSet["Private Working Set"]
privateBytes["Private Bytes"]
virtualBytes["Virtual Bytes 계열"]
privateResident --> workingSet
privateResident --> privateWorkingSet
privateResident --> privateBytes
privateResident --> virtualBytes
privateNonresident --> privateBytes
privateNonresident --> virtualBytes
sharedResident --> workingSet
sharedResident --> virtualBytes
reservedOnly --> virtualBytes
그림 3: Working Set과 Private Bytes는 서로 다른 페이지 집합을 세기 때문에 단순한 포함 관계가 되지 않습니다.
여기서 중요한 점은 Working Set과 Private Bytes가 단순한 포함 관계가 아니다는 것입니다.
Private Bytes에는 프로세스 고유이지만 지금은 RAM에 상주하지 않는 페이지가 들어갑니다. 반면 Working Set에는 DLL 코드나 공유 메모리처럼 Private Bytes에는 세지 않는 공유 페이지가 들어갑니다. 그래서 프로세스나 시점에 따라 Working Set이 Private Bytes보다 클 수도 있고, 그 반대일 수도 있습니다.
또한 여러 프로세스의 Working Set을 단순히 합치면, 공유 DLL처럼 같은 물리 페이지를 여러 번 셀 수 있습니다. 「각 프로세스의 Working Set 합계 = 사용 중인 RAM」이라고는 할 수 없습니다.
3. 가상 주소 공간 ── Reserve와 Commit은 다른 것
3.1. 가상 주소는 물리 RAM의 주소가 아니다
각 프로세스는 자신만의 가상 주소 공간을 가집니다. 앱이 다루는 포인터는 물리 RAM 위치를 직접 가리키지 않으며, Windows가 페이지 테이블로 가상 주소를 물리 페이지나 파일 위의 데이터에 대응시킵니다.7
이 때문에 64GB RAM을 탑재한 PC라도, 어떤 32bit 프로세스가 쓸 수 있는 가상 주소 공간은 보통 그보다 훨씬 작습니다. 반대로 물리 RAM보다 큰 가상 주소 공간을 가진 64bit 프로세스도 흔합니다.
3.2. Reserved는 「주소를 잡아 둔」것뿐
VirtualAlloc의 MEM_RESERVE는 나중에 쓰기 위해 연속된 가상 주소 범위를 확보합니다. 이 단계에서는 페이지에 물리 스토리지가 연결되지 않으며, 그 범위를 읽거나 쓸 수도 없습니다.45
예를 들어 데이터베이스나 런타임이 앞으로의 확장을 위해 8GB 주소 범위를 Reserve해 두었더라도, 그것만으로 8GB RAM이나 8GB Private Bytes를 소비한 것은 아닙니다.
3.3. Committed는 「필요해지면 지원하겠다」고 약속한 상태
MEM_COMMIT은 해당 가상 페이지를 Committed 상태로 만들고, Windows가 필요한 뒷받침을 제공하겠다고 약속하는 작업입니다. 실제로 읽기·쓰기·실행이 허용되는지는 PAGE_READONLY, PAGE_READWRITE, PAGE_EXECUTE, PAGE_NOACCESS 같은 페이지 보호로 따로 정해지며, Commit되었다는 것 자체가 「읽고 쓸 수 있음」을 뜻하지는 않습니다. 커밋한 시점에 시스템의 Commit Charge에 포함되지만, 실제 물리 페이지는 처음 접근할 때까지 할당되지 않을 수 있습니다. 처음 접근한 페이지는 0으로 초기화되고, Demand-zero fault를 거쳐 Working Set에 들어갑니다.51
따라서 같은 「확보했다」라도 다음 세 단계가 있습니다.
flowchart TB
accTitle: Reserve부터 Commit과 RAM 상주까지의 3단계
accDescr: 가상 주소를 예약하고, 페이지를 커밋하고, 최초 접근으로 물리 페이지가 할당되어 Working Set으로 들어가는 흐름을 보여준다
reserve["MEM_RESERVE: 주소 범위 확보"]
reserve -.-> virtualMetric["Virtual Bytes 계열에 반영"]
reserve -->|MEM_COMMIT| committed["Commit됨: 페이지 보호에 따라 접근 가능"]
committed -.-> commitMetric["Private Bytes / System Commit에 반영"]
committed -->|최초 접근·Demand-zero fault| resident["물리 페이지를 할당해 RAM 상주"]
resident -.-> workingSetMetric["Working Set에 반영"]
committed -.->|접근하지 않으면| nonresident["Commit되었지만 비상주"]
그림 4: Reserve, Commit, 최초 접근은 서로 다른 사건이며, 각각 다른 지표를 움직입니다.
이 세 단계가 각각 Virtual Bytes 계열, Private Bytes, Working Set 숫자를 따로 움직입니다.
3.4. 여유 RAM이 있어도 OutOfMemory가 발생하는 이유
메모리 할당의 성패는 여유 RAM만으로 결정되지 않습니다.
- 프로세스의 가상 주소 공간을 다 썼다
- 필요한 크기의 연속된 여유 주소 범위가 없다
- 시스템 전체의 Commit Charge가 Commit Limit에 도달했다
- Job Object, 컨테이너, 런타임, 라이브러리에 자체 상한이 있다
- 32bit 프로세스이다
- 네이티브 힙이 단편화되어 있다
64bit Windows에서도 32bit 프로세스의 사용자 모드 가상 주소 공간은 IMAGE_FILE_LARGE_ADDRESS_AWARE가 꺼져 있으면 보통 2GB입니다. 이 플래그가 켜진 32bit 앱은 64bit Windows에서 최대 4GB까지 사용할 수 있습니다.8
그래서 「PC에는 여유 RAM이 20GB 있는데 32bit 앱이 1.6GB 부근에서 실패한다」는 모순이 아닙니다. RAM 문제가 아니라 주소 공간의 단편화나 상한에 부딪혔을 수 있습니다.
4. Working Set ── 지금 RAM에 올라와 있는 페이지
Working Set은 프로세스의 가상 주소 공간 가운데, 현재 물리 RAM에 상주하고 있는 페이지의 집합입니다.1
여기에는 다음이 섞여 있습니다.
- 프로세스 고유의 힙과 스택
- EXE와 DLL의 코드·읽기 전용 데이터
- 메모리 매핑 파일
- 공유 메모리
- Copy-on-write 이후 그 프로세스 고유가 된 페이지
- 런타임이나 각종 라이브러리가 접근한 페이지
4.1. Working Set이 늘었다고 할당이 늘었다고는 할 수 없다
이미 커밋되어 있던 페이지에 처음 접근하면, Private Bytes는 그대로인 채 Working Set만 늘어나는 경우가 있습니다. 큰 파일을 메모리에 매핑해 순서대로 읽은 경우에도, 파일에서 온 페이지가 Working Set에 들어가고 Private Bytes는 거의 늘지 않을 수 있습니다.
반대로 Windows가 메모리 압박에 맞춰 Working Set을 Trim하면, 앱이 논리적으로는 같은 메모리를 유지한 채로 Working Set만 줄어듭니다. 나중에 다시 접근하면 페이지 폴트를 거쳐 돌아옵니다.
따라서 Working Set이 내려갔다고 「앱이 해제했다」고, 올라갔다고 「앱이 새로 확보했다」고 볼 수는 없습니다.
flowchart TB
accTitle: Working Set만 증감하는 전형적인 흐름
accDescr: 같은 Commit된 페이지가 최초 접근으로 RAM에 들어가고, Trim으로 비상주가 되고, 재접근으로 돌아오는 동안에도 Private Bytes에는 계속 포함된다
committed["같은 Commit된 페이지"]
committed -->|최초 접근| resident["RAM 상주"]
resident -->|메모리 압박으로 Trim| nonresident["비상주"]
nonresident -->|재접근으로 Page Fault| resident
resident -.-> inWorkingSet["Working Set에 포함"]
nonresident -.-> outsideWorkingSet["Working Set에 미포함"]
committed -.-> privateBytes["Commit 중에는 Private Bytes에 포함"]
그림 5: Working Set은 상주 상태에 따라 오르내리지만, 같은 페이지의 Commit이 남아 있는 한 Private Bytes는 줄지 않습니다.
4.2. Working Set에는 공유 페이지가 포함된다
같은 DLL의 코드 페이지를 10개 프로세스가 공유하는 경우, 그 페이지는 각 프로세스의 Working Set에 나타날 수 있지만, 물리 RAM 위에는 한 벌만 있을 수 있습니다. Working Set 합계가 탑재 RAM을 넘더라도 곧바로 이상은 아닙니다.
「지금 이 프로세스만 고유하게 차지하고 있는 RAM」에 가깝게 보려면 Private Working Set을 봅니다. 다만 이것도 「그 프로세스가 확보한 전체 메모리」가 아니라, 어디까지나 현재 상주 중인 Private 페이지입니다.
4.3. Working Set을 강제로 줄여도 누수는 고쳐지지 않는다
EmptyWorkingSet이나 SetProcessWorkingSetSize를 쓰면 프로세스 Working Set에서 페이지를 밀어낼 수 있습니다. 그러나 이는 커밋이나 힙 위의 참조를 해제하는 작업이 아닙니다. Private Bytes는 그대로인 채 겉보기 RAM 사용량만 줄고, 다음 접근에서 페이지 폴트가 늘어날 수 있습니다.9
메모리 절감 버튼을 누른 직후에만 작업 관리자 숫자가 작아지고, 작업을 재개하면 곧 원래로 돌아온다면, 「해제」가 아니라 Working Set을 Trim했을 뿐일 수 있습니다.
5. Private Bytes ── 프로세스 고유의 커밋 양
Private Bytes는 그 프로세스 전용으로 커밋된 가상 메모리의 양입니다. 다른 프로세스와 공유할 수 없는 Commit Charge를 나타내며, 현재 RAM에 상주하는지는 따지지 않습니다. Microsoft의 PROCESS_MEMORY_COUNTERS_EX에서는 PrivateUsage가 이 값에 해당합니다.102
Win32 API에는 PagefileUsage라는 혼동하기 쉬운 필드 이름도 있지만, 현행 문서는 이를 「그 프로세스의 Commit Charge」로 정의하고 PrivateUsage와 같은 값이라고 설명합니다. 즉 Private Bytes 2GB는 「pagefile.sys에 2GB가 기록되어 있다」는 뜻이 아닙니다.2
Private Bytes에는 대표적으로 다음이 영향을 줍니다.
HeapAlloc,malloc,new등이 사용하는 네이티브 힙의 커밋VirtualAlloc으로 직접 커밋한 Private Data- .NET GC 힙의 커밋된 영역
- 스레드 스택 가운데 실제로 커밋된 부분
- Copy-on-write 뷰(
FILE_MAP_COPY)를 매핑할 때 확보되는, 뷰 전체 분량의 Commit Charge - 라이브러리나 디바이스 SDK가 내부에서 보유하는 Private 버퍼
FILE_MAP_COPY로 만드는 Copy-on-write 뷰에서는 각 페이지가 나중에 Private화될 수 있으므로, Windows는 매핑 시점에 뷰 전체를 페이지 파일로 뒷받침할 수 있도록 Commit Charge를 확보합니다. 그래서 실제로 써서 Private 복사본이 만들어지기 전이라도, System Commit과 프로세스의 Commit Charge(Private Bytes)가 뷰 전체만큼 늘어날 수 있습니다.11
5.1. free나 GC 이후에도 Private Bytes가 줄지 않는 이유
애플리케이션 입장에서 메모리를 「해제」해도, 런타임이나 힙 할당자가 그 영역을 OS로 Decommit하지 않고 나중에 재사용하려고 들고 있는 경우가 있습니다. 이때 앱 안에서는 재사용할 수 있어도 Private Bytes는 높은 채로 남습니다.
또한 큰 영역 중 일부만 살아 있다, 단편화되어 있다, 캐시나 풀이 상한까지 채워졌다 같은 이유로도 높은 수준을 유지합니다.
따라서 Private Bytes가 높다는 것만으로는 누수를 증명할 수 없습니다. 봐야 할 것은
- 같은 처리를 같은 횟수만큼 반복한다
- 처리 후 같은 대기 시간을 둔다
- Private Bytes가 같은 수준으로 돌아오는지, 일정 값에서 더 이상 늘지 않는지
- VMMap이나 힙 덤프로 어느 영역·타입이 늘었는지 확인한다
라는 시간 차 비교입니다.
flowchart TB
accTitle: free나 GC 이후에도 Private Bytes가 줄지 않는 이유
accDescr: 앱이 불필요하게 만든 영역을 할당자가 OS로 돌려주는 경우와 재사용을 위해 보유하는 경우에 Private Bytes의 변화가 다르다
release["앱이 free / GC로 불필요하게 만든다"]
release --> decision{"할당자는 OS로 돌려주는가"}
decision -->|Decommit / Release| returned["Commit Charge가 줄어든다"]
returned --> lower["Private Bytes가 줄어든다"]
decision -->|재사용을 위해 보유| retained["영역은 Commit된 상태 그대로"]
retained --> high["Private Bytes는 높은 수준 유지"]
retained --> reasons["풀·캐시·단편화"]
그림 6: 앱 안에서 재사용 가능해진 것과 OS에 Commit을 반환한 것은 같지 않습니다.
5.2. 누수 후보로 유력한 형태
다음과 같이 부하를 줄 때마다 바닥이 올라가는 「계단형」 증가는 주의가 필요합니다.
Private Bytes
^
| ________
| ______|
| ______|
|_____|
+----------------------------> 같은 처리의 반복
다만 계단형이라도 최초 JIT, 폰트, 이미지 디코더, 연결 풀, 캐시 워밍업으로 몇 번만 늘고 그 뒤 안정되는 경우가 있습니다. 늘고 있다는 것보다 정상 상태로 수렴하지 않는다는 것이 중요합니다.
6. System Commit ── 「커밋됨 X/Y」의 정체
작업 관리자의 [성능]→[메모리]에 있는 「커밋됨 X/Y」는 시스템 전체 지표입니다.
- X: System Commit Charge
현재 Windows가 시스템 전체에서 지원하겠다고 약속한 커밋된 메모리 - Y: System Commit Limit
시스템이 감당할 수 있는 커밋 양의 상한
Commit Limit은 대체로 물리 RAM과 모든 페이지 파일의 합계로 결정됩니다. 페이지 파일이 없으면 탑재 RAM보다 조금 작아집니다.36
flowchart TB
accTitle: System Commit Charge와 Commit Limit의 관계
accDescr: 프로세스 고유, 공유 섹션, 커널의 커밋이 현재값 X를 만들고, 물리 RAM과 페이지 파일이 상한 Y를 지원한다
processCommit["각 프로세스의 Private Commit"] --> charge["System Commit Charge: X"]
sharedCommit["페이지 파일로 뒷받침되는 공유 섹션의 Commit"] --> charge
kernelCommit["커널의 Commit"] --> charge
physicalRam["물리 RAM"] --> limit["System Commit Limit: Y"]
pageFiles["페이지 파일"] --> limit
charge -->|X는 Y를 넘을 수 없다| limit
그림 7: X는 현재의 약속량, Y는 그 약속을 감당할 수 있는 상한이며, 페이지 파일 사용량 표시가 아닙니다.
System Commit Charge에는 각 프로세스 Private Bytes의 합계뿐 아니라, 페이지 파일로 뒷받침되는 공유 섹션의 Commit과 커널이 소비하는 Commit도 들어갑니다. 그래서 프로세스별 Private Bytes 합계만으로는 X를 완전히 설명할 수 없습니다.
6.1. Commit Charge는 페이지 파일 사용량이 아니다
RAM 16GB, 페이지 파일 16GB, Committed가 20/31GB인 시스템을 생각해 봅니다.
이 20GB는 「페이지 파일에 20GB를 기록했다」는 뜻이 아닙니다. 20GB는 Private한 변경 가능 페이지 등에 대해, 필요해졌을 때 Windows가 RAM 또는 페이지 파일 등의 뒷받침을 마련하겠다고 약속한 총량입니다.
그 시점에는
- 대부분이 RAM에 상주하고 있다
- 일부가 페이지 파일로 내려가 있다
- Commit되었지만 아직 처음 접근되지 않았다
- 커널 쪽 커밋으로 소비되고 있다
라는 상태가 섞여 있습니다.
페이지 파일의 실제 사용률을 보려면 Commit과 별도로 Paging File(*)\% Usage를 확인합니다. 다만 Microsoft 자료에서도, 페이지 파일 사용률이 높다는 것만으로는 성능 문제라고 할 수 없으며, Commit Limit 도달이나 Modified Page List, 실제 페이징 I/O와 함께 판단하라고 설명합니다.6
6.2. Commit Limit에 가까워지면 무슨 일이 일어나는가
System Commit Charge가 Commit Limit에 도달하면 새로운 커밋 요청을 감당할 수 없습니다. 프로세스의 메모리 할당 실패, 앱 크래시, 조작 불능으로 이어질 수 있습니다.3
여기서는 「여유 RAM」보다 Commit의 X/Y가 중요합니다. Working Set을 Trim해 여유 RAM을 만들어도, Commit Charge 자체가 줄지 않으면 Commit Limit 도달은 해소되지 않습니다.
6.3. 페이지 파일의 세 가지 역할
페이지 파일에는 주로 다음 역할이 있습니다.
- Commit Limit을 확장한다
- 사용 빈도가 낮은 변경된 페이지를 RAM에서 내려 둘 수 있게 한다
- 구성에 따라 시스템 크래시 덤프를 지원한다
페이지 파일을 끄면 디스크 I/O가 반드시 줄어 빨라진다는 단순한 관계는 아닙니다. 오히려 Commit Limit을 낮추고, 변경되었지만 당장은 쓰지 않는 페이지를 RAM에 남기기 쉽게 하며, 크래시 때 필요한 덤프를 채취하지 못하게 할 수 있습니다.36
페이지 파일의 적절한 크기는 탑재 RAM만으로 정해지지 않습니다. Microsoft도 피크 시의 System Commit Charge와 필요한 크래시 덤프 종류가 시스템마다 다르기 때문에 일반화할 수 없다고 설명합니다.6
7. 물리 RAM의 내역 ── Available이 적다는 것만으로는 판단하지 않는다
물리 RAM은 사용자 프로세스의 Working Set에만 쓰이는 것이 아닙니다.
- 각 프로세스의 Working Set
- 시스템 파일 캐시
- Standby, Modified, Free, Zeroed 등의 페이지 리스트
- 커널의 Paged Pool / Nonpaged Pool
- 디바이스 드라이버가 보유하는 메모리
- 메모리 압축 스토어
- GPU나 디바이스와 공유·예약되는 영역
- 하드웨어 예약
7.1. Available에는 재사용 가능한 캐시도 포함된다
Windows의 Available MBytes는 완전히 비어 있는 RAM만 가리키지 않습니다. Free, Zeroed에 더해, 필요하면 재사용할 수 있는 Standby 페이지도 포함하는 지표입니다.12
- Free: 지금 어떤 용도에도 할당되지 않은 페이지
- Zeroed: 다른 프로세스에 안전하게 넘길 수 있도록 0으로 채워 둔 페이지
- Standby: Working Set에서 빠졌지만 내용을 RAM에 캐시해 둔 페이지
- Modified: 내용이 바뀌어, 재사용 전에 적절한 뒷받침으로 다시 써야 하는 페이지
flowchart TB
accTitle: Working Set과 페이지 리스트 사이의 이동
accDescr: 사용 중인 페이지가 변경되지 않았으면 Standby로, 변경되었으면 Modified로 이동하고, 재접근이나 다시 쓰기, 재사용을 거치는 흐름을 보여준다
workingSet["Working Set: 사용 중"]
workingSet -->|변경되지 않은 페이지를 빼냄| standby["Standby: 내용을 유지한 재사용 후보"]
workingSet -->|변경된 페이지를 빼냄| modified["Modified: 다시 쓰기 대기"]
modified -->|다시 쓰기 완료| standby
standby -->|재접근| workingSet
standby -->|다른 용도로 재사용| reused["다른 용도로 할당"]
free["Free: 미사용"] -->|0으로 채움| zeroed["Zeroed: 신규 할당 가능"]
zeroed -->|할당 후 접근| workingSet
standby -.-> available["Available에 포함"]
free -.-> available
zeroed -.-> available
그림 8: Available에는 완전한 빈 공간뿐 아니라, 필요하면 재사용할 수 있는 Standby도 포함됩니다.
「여유 RAM을 늘리려고 캐시를 전부 버린다」가 항상 이득은 아닙니다. Standby에 필요한 데이터가 남아 있으면, 다시 접근할 때 디스크를 읽지 않고 빠르게 Working Set으로 되돌릴 수 있습니다.
따라서 작업 관리자에서 Free가 적더라도, Available이 충분하고 하드 페이지 폴트나 디스크 대기가 문제가 아니면, Windows가 RAM을 캐시로 잘 쓰고 있을 뿐일 수 있습니다.
7.2. 큰 프로세스가 없는데도 RAM이 줄어드는 경우
각 프로세스의 Private Working Set을 더해도 설명되지 않는 메모리 소비는 드물지 않습니다.
- 파일 캐시나 메모리 매핑 파일
- Nonpaged Pool / Paged Pool
- 드라이버가 잠근 페이지
- 공유 페이지
- 메모리 압축
- 가상화나 GPU 관련 할당
이 경우 프로세스 목록만 계속 보기보다, Sysinternals의 RAMMap으로 Use Counts, Processes, Priority Summary, File Summary를 확인합니다. RAMMap은 물리 메모리를 용도별·페이지 리스트별·파일별로 나누어 보는 공식 도구입니다.13
Nonpaged Pool만 계속 늘어나는 경우라면, 사용자 모드 앱의 Private Bytes가 아니라 드라이버나 커널 쪽 누수를 의심할 단계입니다.
8. Page Fault ── 많다는 것만으로는 이상이 아니다
프로세스가 현재 Working Set에 없는 페이지에 접근하면 Page Fault가 발생합니다. 이름에 「Fault」가 들어 있지만, 예외적인 고장이 아니라 가상 메모리를 돌리는 보통 메커니즘입니다.1
8.1. 소프트 페이지 폴트
디스크를 읽지 않고 끝나는 것입니다.
- 페이지가 Standby나 Transition에 남아 있다
- 다른 프로세스의 Working Set에 같은 공유 페이지가 있다
- Commit된 페이지에 처음 접근해 제로 페이지를 할당한다
- 메모리 관리자의 선행 읽기로 이미 RAM에 있다
이 때문에 \Memory\Page Faults/sec가 커도 디스크 I/O나 지연이 생긴다고는 할 수 없습니다.
8.2. 하드 페이지 폴트
디스크의 Backing Store에서 내용을 읽어야 하는 것입니다. 읽는 곳은 페이지 파일만이 아닙니다.
.exe나.dll의 코드·데이터- 메모리 매핑 파일
- 페이지 파일
flowchart TB
accTitle: 소프트 페이지 폴트와 하드 페이지 폴트의 분기
accDescr: Working Set에 없는 페이지에 접근했을 때, 스토리지 I/O가 필요 없으면 소프트 페이지 폴트, 필요하면 하드 페이지 폴트로 처리한다
access["Working Set에 없는 페이지에 접근"] --> storageIo{"스토리지 I/O가 필요한가"}
storageIo -->|아니요: Standby·공유·Demand-zero 등| soft["소프트 페이지 폴트"]
soft --> resident["디스크를 읽지 않고 Working Set으로"]
storageIo -->|예| hard["하드 페이지 폴트"]
hard --> source{"어디서 읽는가"}
source --> image["EXE / DLL"]
source --> mapped["메모리 매핑 파일"]
source --> pagefile["페이지 파일"]
image --> loaded["읽은 뒤 Working Set으로"]
mapped --> loaded
pagefile --> loaded
그림 9: Page Fault라는 이름만으로는 디스크 I/O 여부를 판단할 수 없습니다.
Microsoft는 하드 폴트를 재는 카운터로 \Memory\Pages/sec, \Memory\Page Reads/sec, \Memory\Pages Input/sec 등을 들고 있습니다. 이 값들이 높다고 반드시 메모리 부족은 아니므로, Available MBytes, 디스크 지연, 실제 응답 시간과 함께 상관을 봅니다.6
8.3. 일률적인 임계값을 두지 않는다
「Page Faults/sec가 1000을 넘으면 이상」 같은 고정값은 스토리지, 페이지 크기, 워크로드, 접근 지역성에 따라 의미가 달라집니다.
실무에서는 다음을 같은 시간축에 나란히 둡니다.
Memory\Available MBytesMemory\Pages Input/secMemory\Page Reads/sec- 대상 디스크의 Read latency / Queue
- 대상 프로세스의 Working Set·Private Bytes
- 앱의 처리 시간, 타임아웃, UI 응답
부하 증가와 함께 Available이 줄고, Pages Input/sec와 디스크 대기가 올라가고, 처리 시간도 나빠지면, 물리 메모리 압박에 의한 페이징을 의심할 근거가 갖춰집니다.
9. 어느 화면·도구에서 무엇을 볼 것인가
| 알고 싶은 것 | 처음에 볼 지표 | 주요 도구 |
|---|---|---|
| 지금 대상 프로세스가 RAM에 올린 양 | Working Set | 작업 관리자, Process Explorer, Get-Process |
| 그중 프로세스 고유의 RAM | Private Working Set / Working Set - Private | 작업 관리자의 세부 정보 열, Process Explorer, PerfMon |
| 대상 프로세스 고유의 커밋 양 | Private Bytes / Commit Size | Process Explorer, PerfMon, VMMap, Get-Process |
| 프로세스의 가상 주소 범위 | Virtual Bytes / Size | Process Explorer, VMMap, Get-Process |
| 시스템 전체의 커밋 여력 | Committed Bytes / Commit Limit | 작업 관리자 [성능], PerfMon |
| 물리 RAM의 재사용 여력 | Available MBytes | 작업 관리자, PerfMon |
| Standby·Modified·파일 캐시의 내역 | 페이지 리스트·용도별 내역 | RAMMap |
| Private Bytes 중 무엇이 늘었는가 | Heap / Private Data / Managed Heap 등 | VMMap, WinDbg, 런타임별 덤프 |
| 디스크를 동반하는 페이징 | Pages Input/sec, Page Reads/sec, 디스크 지연 | PerfMon, WPR/WPA |
flowchart TB
accTitle: Windows 메모리 조사 도구 고르기
accDescr: 대상이 1개 프로세스인지 시스템 전체인지, 한 시점인지 시계열인지, 런타임 내부까지 추적할지에 따라 사용할 도구를 고른다
question["무엇을 가려내고 싶은가"]
question --> processScope{"대상은 1개 프로세스인가"}
processScope -->|예| processTime{"한 시점인가 시계열인가"}
processTime -->|한 시점의 내역| vmmap["VMMap"]
processTime -->|시계열| perfmon["PerfMon / PowerShell"]
processScope -->|시스템 전체| systemView{"물리 RAM의 내역인가 시간축인가"}
systemView -->|물리 RAM의 내역| rammap["RAMMap"]
systemView -->|CPU·I/O·대기까지 포함한 시간축| wpa["WPR / WPA"]
question --> runtime{"런타임 내부의 보유까지 추적하는가"}
runtime -->|.NET 힙| dotnet["dotnet-dump / PerfView"]
runtime -->|네이티브 힙| native["WinDbg / Application Verifier"]
그림 10: 먼저 대상 범위와 시간축을 정하면 필요한 도구를 과부족 없이 고를 수 있습니다.
9.1. 작업 관리자
작업 관리자에서는 화면을 나누어 봅니다.
- [프로세스]또는[세부 정보]: 개별 프로세스의 Working Set 계열, Commit Size 계열
- [성능]→[메모리]: 시스템 전체의 In use, Available, Committed, Cached, Paged pool, Non-paged pool
「메모리」라는 열 이름만으로 판단하지 말고, [세부 정보]탭의 열 머리글을 마우스 오른쪽 버튼으로 클릭해 Working Set, Peak Working Set, Commit Size 등 필요한 열을 추가합니다. Windows 버전이나 표시 언어에 따라 열 이름이 다소 다르므로, 열의 의미를 확인한 뒤에 기록합니다.
9.2. PowerShell로 시계열 데이터 수집하기
대상 프로세스의 ID를 알고 있다면, Get-Process로 Working Set, Private Bytes, Virtual Bytes의 기울기를 동시에 잡을 수 있습니다.
param(
[Parameter(Mandatory)]
[int]$ProcessId,
[int]$IntervalSeconds = 5,
[int]$SampleCount = 60
)
$samples = for ($i = 0; $i -lt $SampleCount; $i++) {
$process = Get-Process -Id $ProcessId -ErrorAction Stop
[pscustomobject]@{
Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
ProcessId = $process.Id
WorkingSetMB = [math]::Round($process.WorkingSet64 / 1MB, 1)
PrivateBytesMB = [math]::Round($process.PrivateMemorySize64 / 1MB, 1)
VirtualBytesMB = [math]::Round($process.VirtualMemorySize64 / 1MB, 1)
Handles = $process.HandleCount
Threads = $process.Threads.Count
}
Start-Sleep -Seconds $IntervalSeconds
}
$samples | Format-Table -AutoSize
$samples | Export-Csv .\memory-samples.csv -NoTypeInformation -Encoding utf8
.NET의 Process.WorkingSet64는 Working Set, PrivateMemorySize64는 Private Bytes, VirtualMemorySize64는 Virtual Bytes에 대응합니다.141516
인스턴스가 여러 개인 앱에서는 이름이 아니라 PID로 추적하세요. 재시작으로 PID가 바뀌는 장기 모니터링에서는 시작 시각이나 서비스 이름 등을 기록해 대상을 헷갈리지 않도록 설계해야 합니다.
9.3. PerfMon으로 시스템과 프로세스를 같은 시간축에 놓기
최소한 다음을 동시에 기록하면 원인을 가려내기 쉬워집니다.
\Process(<대상>)\ID Process
\Process(<대상>)\Working Set
\Process(<대상>)\Working Set - Private
\Process(<대상>)\Private Bytes
\Process(<대상>)\Virtual Bytes
\Memory\Available MBytes
\Memory\Committed Bytes
\Memory\Commit Limit
\Memory\Pages Input/sec
\Memory\Page Reads/sec
\Memory\Pool Nonpaged Bytes
\Memory\Pool Paged Bytes
같은 이름의 프로세스가 여러 개이거나 모니터링 중에 재시작되면, Process(name)이나 Process(name#N) 인스턴스 이름만으로는 대상을 고정할 수 없습니다. 각 샘플에서 ID Process도 기록하고, 그 값이 추적 대상 PID와 일치하는 인스턴스만 채택합니다. PID가 바뀌는 재시작을 넘나들면 전환된 시각도 따로 기록합니다.
Windows의 성능 카운터 이름은 표시 언어에 따라 현지화되는 경우가 있습니다. PowerShell에서 영어 이름을 직접 지정했는데 찾을 수 없다면, PerfMon GUI에서 추가하거나 Get-Counter -ListSet *로 로컬 환경의 이름을 확인합니다.
9.4. VMMap과 RAMMap의 역할을 혼동하지 않는다
- VMMap: 프로세스 하나의 가상 메모리와 Working Set을 Heap, Image, Mapped File, Private Data, Managed Heap 등으로 분해한다
- RAMMap: 시스템 전체의 물리 RAM을 용도, 페이지 리스트, 프로세스, 파일로 분해한다
「이 프로세스의 Private Bytes는 무엇 때문에 늘었는가」는 VMMap, 「프로세스 목록으로 설명되지 않는 RAM은 어디에 쓰였는가」는 RAMMap입니다.1713
10. 숫자의 조합에서 증상을 읽기
| 관측된 형태 | 먼저 생각할 것 | 다음에 확인할 것 |
|---|---|---|
| Working Set이 늘고 Private Bytes는 안정 | 기존 페이지에 대한 최초 접근, 공유 DLL, 매핑 파일, 파일 캐시 | VMMap의 Image / Mapped File, Pages Input/sec |
| Private Bytes가 늘고 Working Set은 안정 | Private한 Commit은 늘었지만 비상주이거나 Trim된 상태 | VMMap의 Heap / Private Data / Managed Heap |
| 둘 다 시작 직후에 늘고 그 후 평탄 | JIT, 캐시, 풀, 초기화 워밍업 | 같은 부하를 더해도 다시 늘어나는지 |
| 부하를 줄 때마다 Private Bytes의 바닥이 올라간다 | 누수, 상한 없는 캐시, 해제 후에도 들고 있는 할당자 | 전후 VMMap 스냅샷, 힙 덤프 |
| Working Set만 갑자기 줄고 조작하면 돌아온다 | OS 또는 앱이 Working Set을 Trim했다 | Private Bytes, Pages Input/sec, 응답 시간 |
| Committed X/Y의 X가 Y에 가까워진다 | 시스템 전체의 커밋 압박 | Private Bytes 상위, Paged/Nonpaged Pool, 페이지 파일 설정 |
| Available이 낮고 Pages Input/sec와 디스크 지연이 높다 | 물리 RAM 압박과 하드 페이징 | Working Set 상위, RAMMap, 워크로드 상관 |
| RAM 사용률은 높은데 큰 프로세스가 없다 | 캐시, 공유 페이지, 커널 풀, 드라이버, 압축 등 | RAMMap, Pool Nonpaged/Paged Bytes |
| 여유 RAM이 있는데 32bit 앱만 실패 | 가상 주소 공간 상한·단편화 | VMMap의 Free/Reserved, 실행 파일의 LAA 설정 |
| Private Bytes는 높은데 처리를 반복해도 늘지 않는다 | 높은 수위를 유지하는 풀·캐시일 가능성 | 상한, 재사용 상황, 피크 이후의 안정성 |
이 표에서 가장 중요한 것은 단독 값이 아니라 조합으로 읽는 것입니다.
11. 메모리 누수 조사 실무 절차
11.1. 먼저 재현 조건과 정상점을 정한다
「며칠 만에 늘어난다」만으로는 비교할 수 없습니다.
- 시작 후 워밍업을 어디까지 넣을 것인가
- 1사이클의 조작 내용
- 1사이클 후 몇 초 기다릴 것인가
- 캐시 상한에 닿을 때까지 몇 번이 필요한가
- 정상 버전·문제 버전에서 같은 입력을 쓸 수 있는가
를 정합니다.
11.2. 프로세스와 시스템을 동시에 기록한다
최소한 다음을 같은 시각에 남깁니다.
- 대상의 Working Set
- 대상의 Private Bytes
- 대상의 Virtual Bytes
- 시스템의 Committed Bytes / Commit Limit
- Available MBytes
- Pages Input/sec
- 핸들 수, 스레드 수
- 조작 횟수나 처리 건수
프로세스의 Private Bytes는 안정적인데 시스템의 Commit만 늘어난다면, 다른 프로세스, 커널, 드라이버, 공유 섹션으로 시야를 넓혀야 합니다.
11.3. 늘고 있는 「차원」을 먼저 정한다
- Working Set만: 상주 페이지, 공유·파일 유래, Trim과 다시 읽기
- Private Bytes: 프로세스 고유 커밋
- Virtual Bytes만: Reserve, 매핑, 주소 공간 단편화
- System Commit만: 다른 프로세스나 커널 쪽도 포함
- Nonpaged Pool: 드라이버·커널 쪽
- Handles / GDI / USER: 메모리 이외의 자원 누수
이 순서를 건너뛰고 바로 덤프를 뜨면, 대상이 다른 채로 많은 정보를 읽게 됩니다.
11.4. 내역으로 나아간다
- 네이티브 프로세스: VMMap, WinDbg, Application Verifier, 힙 트레이스
- .NET:
dotnet-counters,dotnet-gcdump,dotnet-dump, PerfView - 시스템 전체: RAMMap, PerfMon, WPR/WPA
- 커널 풀: PoolMon, WinDbg
VMMap은 프로세스의 커밋된 가상 메모리와, 각각에 할당된 Working Set을 종류별로 보여 줍니다. Private Bytes 증가를 「Heap」「Private Data」「Managed Heap」「Mapped File」 중 어디까지 좁히느냐에 따라, 이후 조사 비용이 크게 달라집니다.17
11.5. 수정 후에는 같은 조건에서 기울기를 비교한다
수정 전후로 피크 값만 다르다는 것으로는 부족합니다. 초기값이 다르면 쉽게 뒤집힙니다.
- 같은 시작 상태
- 같은 입력
- 같은 조작 횟수
- 같은 대기 시간
- 같은 샘플링 간격
으로 각 사이클 후의 바닥값과 기울기를 비교합니다. 누수 수정의 증명은 「최댓값이 작아졌다」가 아니라, 같은 부하를 반복해도 증가가 수렴하게 되었다는 것입니다.
12. 흔한 오해를 바꿔 말하기
오해 1: 작업 관리자의 메모리 = 앱이 확보한 전체 양
바꿔 말하면: 어느 열인지 확인합니다. Working Set 계열이면 현재 RAM에 상주하는 양, Commit Size 계열이면 프로세스 고유의 커밋 양입니다.
오해 2: Private Bytes = 페이지 파일 위의 바이트 수
바꿔 말하면: Private Bytes는 Private한 Commit Charge입니다. RAM에 있는 페이지도, 필요할 때 페이지 파일로 뒷받침되는 페이지도 포함하는 논리적인 약속량입니다.
오해 3: Commit X/Y = 페이지 파일 사용량/페이지 파일 용량
바꿔 말하면: X는 시스템 전체의 Commit Charge, Y는 Commit Limit입니다. 페이지 파일은 Y를 넓히지만, X가 그대로 디스크 위의 사용량이 되는 것은 아닙니다.
오해 4: Page Faults/sec가 높다 = 디스크로 스왑하고 있다
바꿔 말하면: 소프트 폴트도 포함합니다. 디스크 I/O를 동반하는지는 Pages Input/sec나 Page Reads/sec와 디스크 지연으로 확인합니다.
오해 5: Free RAM이 적다 = 메모리 부족
바꿔 말하면: Available, Standby, 하드 페이징, 응답 시간을 봅니다. 재사용 가능한 캐시로 RAM을 채우는 것은 정상입니다.
오해 6: Working Set을 줄일 수 있었다 = 메모리 누수를 고쳤다
바꿔 말하면: 페이지를 RAM에서 밀어냈을 뿐일 수 있습니다. Private Bytes나 힙 안의 보유가 줄었는지 확인합니다.
오해 7: Private Bytes가 늘었다 = 누수 확정
바꿔 말하면: 같은 워크로드를 반복했을 때 수렴하는지, 어떤 종류의 메모리가 늘었는지, 해제 가능한 캐시인지를 확인해야 비로소 판단할 수 있습니다.
13. 정리
- Windows의 「메모리 사용량」은 하나의 숫자가 아닙니다. 주소 공간, 커밋, RAM 상주, 공유 가능성을 나누어 생각합니다.
- Working Set은 현재 RAM에 있는 페이지로, Private와 Shared를 모두 포함합니다. Private Working Set은 그중 프로세스 고유의 상주 페이지입니다.
- Private Bytes는 프로세스 고유의 Commit Charge이며, 현재 RAM에 있는 양도, 페이지 파일에 실제로 기록된 양도 아닙니다.
- Committed X/Y는 시스템 전체의 Commit Charge / Commit Limit입니다. 페이지 파일은 주로 Commit Limit, 변경된 페이지를 RAM에서 내려 두는 일, 크래시 덤프를 지원합니다.
- Reserve한 가상 주소, Commit한 페이지, 실제로 접근해 Working Set에 들어간 페이지는 서로 다른 단계입니다.
- Page Fault는 보통 동작이며, 소프트 폴트는 디스크를 읽지 않습니다. 하드 폴트도 페이지 파일뿐 아니라 EXE, DLL, 매핑 파일에서 발생합니다.
- 메모리 누수는 한 시점의 크기가 아니라, 같은 부하 후의 바닥값과 기울기, 그리고 내역으로 증명합니다.
- 개별 프로세스의 내역은 VMMap, 시스템 전체의 물리 RAM은 RAMMap, 시계열은 PerfMon, 런타임 내부는 전용 덤프 도구로 나아가는 것이 기본입니다.
다음에 작업 관리자에서 「메모리가 늘고 있다」고 알아차리면, 먼저 이렇게 다시 묻습니다.
늘고 있는 것은 Working Set인가, Private Bytes인가, Virtual Bytes인가, System Commit인가.
이 질문만으로도 조사의 입구는 꽤 정확해집니다.
관련 글
- .NET에서 GC 대기와 메모리 누수를 가려내기 ── 늘어나는 메모리를 관측·비교·증명하는 실무 절차
- Process Explorer / Handle / VMMap 실전 ── Hang·누수·「파일이 사용 중」을 지금 이 순간의 상태에서 추적하기
- 공유 메모리의 함정과 모범 사례 ── Windows에서 프로세스 간 공유를 안전하게 설계하기
- Windows Cache Manager의 지연 쓰기 ── WriteFile에서 디스크 반영까지
- Windows 앱의 핸들 누수로 장기 가동 후 크래시하는 원인과 대책
관련 상담 영역
합동회사 코무라소프트에서는 Windows 앱의 메모리 증가, 장기 가동 후 성능 저하, 32bit 프로세스의 OutOfMemory, 고객 환경에서만 발생하는 메모리 부족에 대해, PerfMon·VMMap·RAMMap·WinDbg·.NET 진단 도구를 조합한 원인 조사를 다룹니다. 단순히 「메모리가 많다」로 끝내지 않고, 어느 영역이, 어떤 조작으로, 왜 늘었으며, 어디서 참조·보유되고 있는지까지 가려냅니다.
참고 링크
-
Microsoft Learn, Working Set. 프로세스의 Working Set이 현재 물리 메모리에 상주하는 페이지의 집합이라는 점, 공유 페이지를 포함한다는 점, 소프트·하드 페이지 폴트의 차이, Transition 페이지, Working Set에서의 페이지 제거에 대해. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, PROCESS_MEMORY_COUNTERS_EX2 structure. WorkingSetSize, PrivateWorkingSetSize, PrivateUsage, SharedCommitUsage의 정의, PagefileUsage와 PrivateUsage가 모두 프로세스의 Commit Charge를 나타낸다는 점에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Introduction to page files. 페이지 파일이 변경된 페이지를 RAM에서 내려 두는 일, 시스템 크래시 덤프, System Commit Limit의 확장을 지원한다는 점, System Commit Charge와 Commit Limit의 정의, 작업 관리자 및 성능 카운터를 이용한 측정에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Page State. 가상 페이지의 Free, Reserved, Committed 각 상태와, Reserved 페이지에는 물리 스토리지가 연결되지 않아 접근할 수 없다는 점에 대해. ↩ ↩2
-
Microsoft Learn, VirtualAlloc function. MEM_RESERVE와 MEM_COMMIT의 차이, Commit 시 시스템 전체의 메모리와 페이지 파일에서 Charge된다는 점, 실제 물리 페이지는 최초 접근까지 할당되지 않는 경우가 있다는 점에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, How to determine the appropriate page file size for 64-bit versions of Windows. 페이지 파일의 크기가 피크 Commit Charge와 크래시 덤프 요건에 좌우된다는 점, 하드 페이지 폴트가 읽어 오는 곳이 페이지 파일에 국한되지 않고 EXE·DLL·메모리 매핑 파일을 포함한다는 점, 관련 성능 카운터에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Virtual Address Space. 각 프로세스가 독립된 가상 주소 공간과 페이지 테이블을 가지며, 가상 주소가 물리 주소 그 자체는 아니라는 점에 대해. ↩
-
Microsoft Learn, Memory Limits for Windows and Windows Server Releases. 32bit 프로세스의 사용자 모드 가상 주소 공간이 보통 2GB이며, 64bit Windows에서는 IMAGE_FILE_LARGE_ADDRESS_AWARE의 유무에 따라 2GB 또는 4GB가 된다는 점에 대해. ↩
-
Microsoft Learn, SetProcessWorkingSetSize function. Working Set의 최소·최대값이 상주를 보장하는 것은 아니라는 점, Working Set을 비울 수 있다는 점, 과도한 설정이나 조작이 시스템 성능을 악화시킬 수 있다는 점에 대해. ↩
-
Microsoft Learn, Memory Performance Information. Windows의 성능 카운터, 메모리 관리 API, 작업 관리자 표시 간의 대응 관계, Process의 Working Set·Working Set - Private·Private Bytes, System의 Committed Bytes·Commit Limit에 대해. ↩
-
Microsoft Learn, MapViewOfFile function.
FILE_MAP_COPY에서는 모든 페이지가 Copy-on-write가 될 수 있기 때문에, 뷰 전체를 페이지 파일로 뒷받침할 수 있도록 매핑 시점에 Commit Charge가 확보된다는 점에 대해. ↩ -
Microsoft Learn, Understanding Node Metrics and Properties in HPC Cluster Manager. Available Physical Memory가 Zeroed, Free, Standby 각 리스트의 합계로 계산된다는 점과, 각 페이지 리스트의 의미에 대해. ↩
-
Microsoft Sysinternals, RAMMap. Windows의 물리 메모리 사용량을 용도, 페이지 리스트, 프로세스, 우선순위, 물리 페이지, 파일 단위로 분석하는 기능에 대해. ↩ ↩2
-
Microsoft Learn, Process.WorkingSet64 Property.
WorkingSet64가 프로세스의 Working Set을 바이트 단위로 반환하며, Process의 Working Set 성능 카운터에 대응한다는 점에 대해. ↩ -
Microsoft Learn, Process.PrivateMemorySize64 Property.
PrivateMemorySize64가 다른 프로세스와 공유할 수 없는 프로세스 고유 메모리를 반환하며, Private Bytes 성능 카운터에 대응한다는 점에 대해. ↩ -
Microsoft Learn, Process.VirtualMemorySize64 Property.
VirtualMemorySize64가 프로세스의 가상 메모리량을 반환하며, Virtual Bytes 성능 카운터에 대응한다는 점에 대해. ↩ -
Microsoft Sysinternals, VMMap. 프로세스의 커밋된 가상 메모리를 종류별로 분해하고, 각각에 할당된 물리 메모리(Working Set)와 상세한 메모리 맵을 표시하는 기능에 대해. ↩ ↩2
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows 메모리의 심층(제2회) ── 물리 페이지의 일생: 다섯 목록과 페이지 파일의 진실
PFN 데이터베이스, Standby, Modified, 메모리 압축, 페이지 파일을 이어서 Working Set에서 벗어난 물리 페이지의 행선을 설명합니다.
절전에서 재개하면 깨지는 앱 ── 전원 이벤트의 구조와 재개에 강한 업무 앱을 만드는 방법
노트북을 열었더니 업무 앱의 통신이 끊어져 있었다――원인은 절전을 전제하지 않은 설계입니다. WM_POWERBROADCAST에 의한 알림의 흐름, Modern Standby의 동작, 끊김·재연결 설계, 절전 억제와 조사 명령까지를 1차 정보로 설...
「응답 없음」의 정체 ── Windows가 앱을 「멈췄다」고 판단하는 구조와, 멈추지 않는 설계
Windows의 「응답 없음」은 창이 5초 동안 메시지를 꺼내지 않으면 OS가 판단해 고스트 창으로 교체하는 구조입니다. 판단의 내부 동작부터 멈추는 전형적인 원인, UI 스레드에서 무거운 처리를 빼내는 설계, hang 조사 절차까지 설명합니다.
WPR/WPA 실무 ── 「PC 전체가 느리다」를 시스템 전체에서 조사하는 성능 조사 입문
「PC 전체가 느리다」「부팅이 느리다」처럼 작업 관리자로는 따라갈 수 없는 성능 문제는 OS 전체 ETW 트레이스를 모아 읽는 WPR/WPA로 조사합니다. wpr.exe 수집 절차부터 WPA에서 CPU·대기·디스크 I/O를 읽는 법까지 설명합니다.
Windows 메모리의 심층(제1회) ── 가상 주소가 물리 RAM으로 바뀌는 순간: 페이지 폴트의 전말
VirtualAlloc, VAD, 페이지 테이블, TLB, demand-zero, 하드 폴트를 이어서 가상 주소에 물리 RAM이 할당되는 순간을 설명합니다.
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
장애 조사 & 장기 가동 장애
간헐적 장애, 통신 진단, 장기 가동 크래시, 실패 경로 테스트 기반을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
장애 조사 & 원인 분석
간헐적 장애, 장기 가동 중 크래시, 누수, 통신 중단 등 까다로운 프로덕션 이슈를 조사합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 작업 관리자의 「메모리」는 앱이 확보한 전체 메모리인가요?
- 아닙니다. 작업 관리자에는 Working Set 계열, Private Working Set 계열, Commit Size 등 여러 메모리 열이 있으며, 어느 화면·열을 보는지에 따라 의미가 달라집니다. Working Set은 현재 RAM에 올라와 있는 페이지, Private Bytes 또는 Commit Size는 그 프로세스 고유의 커밋 양입니다. 하나의 「메모리」열을 앱이 확보한 전체 용량이나 누수 양으로 바꿔 읽지 마세요.
- Working Set과 Private Bytes는 무엇이 다른가요?
- Working Set은 그 프로세스에서 볼 수 있으며 현재 물리 RAM에 상주하고 있는 페이지의 양으로, DLL이나 메모리 매핑 파일 등의 공유 페이지도 포함합니다. Private Bytes는 그 프로세스만 사용하는 커밋된 메모리의 양으로, 현재 RAM에 상주하고 있는지 여부는 따지지 않습니다. 따라서 두 값은 같은 값이 되지 않으며, 단순한 대소 관계도 아닙니다.
- 작업 관리자의 「커밋됨 18/32GB」는 18GB를 페이지 파일에 기록하고 있다는 의미인가요?
- 아닙니다. 왼쪽 숫자는 시스템 전체가 현재 약속하고 있는 커밋 양, 오른쪽 숫자는 시스템이 지원할 수 있는 커밋 상한입니다. 상한은 대체로 RAM과 페이지 파일의 합계로 결정되지만, 왼쪽의 전체 양이 페이지 파일 상에 있는 것은 아닙니다. 커밋된 페이지 중 상당수는 RAM에 있으며, 아직 한 번도 물리 페이지가 할당되지 않은 페이지도 있습니다. 한편 EXE·DLL·메모리 매핑 파일처럼 원본 파일에서 다시 읽어 올 수 있는 페이지는, Working Set을 늘려도 Private한 Commit을 그만큼 늘린다고는 할 수 없습니다.
- 여유 RAM이 있는데도 OutOfMemory가 발생할 수 있나요?
- 있습니다. 32bit 프로세스의 가상 주소 공간 부족, 연속된 여유 주소 범위의 부족, 시스템의 커밋 상한, Job Object나 런타임 고유의 상한 등, 물리 RAM 이외에도 할당 실패의 조건이 있기 때문입니다. 특히 64bit Windows 상의 32bit 프로세스는 Large Address Aware가 아니면 보통 2GB의 사용자 모드 가상 주소 공간이 상한입니다.
- 페이지 파일을 비활성화하면 Windows가 빨라지나요?
- 일반적으로는 빨라진다고 단정할 수 없습니다. 페이지 파일을 비활성화하면 시스템의 커밋 상한이 낮아지고, 사용되지 않는 변경된 페이지를 RAM에서 대피시키기 어려워지며, 크래시 덤프의 구성에도 영향을 줍니다. 페이지 파일의 크기는 피크 시의 커밋 양과 필요한 크래시 덤프를 측정한 뒤에 결정해야 하며, 근거 없이 비활성화할 설정이 아닙니다.
- Page Faults/sec가 많으면 메모리 부족인가요?
- 그것만으로는 판단할 수 없습니다. 페이지 폴트에는 RAM 내의 Standby 페이지나 다른 프로세스와 공유 중인 페이지로 해결할 수 있는 소프트 폴트와, 디스크에서 읽어 오는 하드 폴트가 있습니다. Page Faults/sec 단독이 아니라 Pages Input/sec나 Page Reads/sec, Available MBytes, 디스크 지연, 처리 시간을 같은 시간축에서 함께 확인하세요.