Process Explorer / Handle / VMMap 실전 ── hang·누수·「파일 사용 중」을 지금 이 순간의 상태에서 추적하기
· 업데이트: · Go Komura · Sysinternals, Process Explorer, VMMap, 핸들 누수, 메모리 누수, 장애 조사, Windows, 장기 가동
수정 이력(5건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 기사 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건)에 대응해 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참조하세요.
- for 명령의 변수 표기에 대해 「배치 파일 안에서도 직접 실행해도 같다」는 잘못된 주석을 수정했습니다. 배치 파일 안에서는 %%d, 명령 프롬프트에 직접 입력할 때는 %d로 구분해 써야 합니다.
- Process Explorer와 VMMap 조작을 메뉴 이름 그대로의 번호 매긴 절차로 다시 썼습니다. 아울러 전제 환경, dbghelp.dll 입수 방법, 전편 해당 절의 요점, 이름으로 검색할 수 있는 것은 이름이 있는 객체뿐이라는 제약을 정리에도 넣었습니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22174947)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「Process Explorer / Handle / VMMap 실전 ── hang·누수·「파일 사용 중」을 지금 이 순간의 상태에서 추적하기」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/sysinternals-process-explorer-handle-vmmap-guide/
- DOI(등록된 아카이브)
- 10.5281/zenodo.22174947
- DOI(마지막 등록 버전)
- 10.5281/zenodo.22174948
「월요일 아침에 재부팅하면 고쳐집니다」── 장비 연동 앱 유지보수 상담에서 여러 번 들은 말입니다. 가동 직후에는 잘 돌아가다가 목요일쯤부터 화면 전환이 더뎌지고, 금요일 저녁에는 버튼을 눌러도 몇 초가 지나야 반응이 옵니다. 가끔 「핸들이 잘못되었습니다」라는 본 적 없는 오류가 나기도 합니다. 그리고 재부팅하면 아무 일도 없었다는 듯이 돌아갑니다. 이렇게 되면 운영은 「매주 재부팅」으로 고정되고, 근본 원인은 아무도 모른 채 몇 년이 지나갑니다.
이런 증상은 로그를 아무리 봐도 원인에 닿지 않습니다. 필요한 것은 「지금 이 순간, 그 프로세스가 무엇을 얼마나 붙들고 있는지」를 직접 보는 일입니다. 지난번 「Process Monitor(ProcMon) 실전 가이드」에서는 조작의 시계열을 기록하는 ProcMon을 다뤘습니다. 이번에는 그 속편이자 Sysinternals 실전 제2탄으로, 상태를 보는 쪽 도구 ── Process Explorer·Handle·VMMap ── 를 장기 가동 앱의 3대 증상 「점점 무거워진다」「파일을 지울 수 없다」「hang이 걸렸다」에 맞춰 정리합니다.
이 글의 전제 환경은 다음과 같습니다.
- OS ── Process Explorer의 공식 동작 요건은 클라이언트가 Windows 11 이상, 서버가 Windows Server 2016 이상입니다.1
- 비트 수 ── 64bit 환경에서는
procexp.exe를 실행하면 64bit 버전이 자동으로 풀려 실행됩니다. 64bit 프로세스의 스택이나 핸들을 올바르게 보려면 이 64bit 버전으로 동작해야 합니다. - 권한 ── 조사 목적이라면 「관리자 권한으로 실행」이 사실상 필수입니다. 일반 권한 그대로면 다른 사용자나 서비스 프로세스에 한해 스레드 스택도 핸들 목록도 「액세스가 거부되었습니다」가 됩니다. CLI 버전
handle.exe는 공식 문서상 관리자 권한이 필요합니다.2 - 최초 실행 ── EULA(사용권 계약) 동의 대화상자가 뜹니다. 스크립트나 무인 실행에서는
/accepteula스위치로 동의를 명시합니다(8장).
이 글은 GUI 도구 조작을 다루지만, 화면 캡처는 싣지 않습니다. 대신 메뉴 이름과 바로 가기 키를 실제 표기 그대로 적었습니다. 도구를 열어 두고 읽을 수 있게 했습니다.
1. 먼저 결론
- ProcMon이 「이력」, Process Explorer는 「현재」입니다. Process Explorer는 각 프로세스가 연 핸들과 로드한 DLL을 나열·검색하는 도구이며, DLL 버전 문제나 핸들 누수 조사에 맞다고 공식도 명시합니다.1
- 「파일을 지울 수 없다·교체할 수 없다」는 Find Handle or DLL(Ctrl+F)이 가장 빠릅니다. 경로 일부로 검색하면 그 파일을 잡고 있는 프로세스를 몇 초 만에 찾을 수 있습니다(3장).
- 「hang이 걸렸다」는 프로세스 속성 → Threads 탭에서 스레드 스택을 봅니다. 다만 심볼 설정(dbghelp.dll과 심볼 경로)을 하지 않으면 주소 나열만 나옵니다(4장).13
- 「점점 무거워진다」는 컬럼을 더해 기울기를 봅니다. Handle Count·USER Objects·GDI Objects·Private Bytes 네 가지를 추가하고, 시간을 두고 계속 늘어나는 것을 찾습니다. GDI/USER 객체에는 프로세스별 상한이 있어, 도달하면 그리기나 창 생성이 깨지기 시작합니다(5장).4
- CLI 버전 handle.exe는 스크립트 정기 관측에 맞습니다.
handle -s -p <프로세스>로 종류별 핸들 수를 주기적으로 기록할 수 있습니다.-c로 핸들을 강제 닫는 것은 공식이 불안정화를 경고하므로 기본적으로 쓰지 않습니다.2 - 「Private Bytes가 계속 늘면」 내역은 VMMap으로 나눕니다. Heap이면 네이티브, Managed Heap이면 .NET, 이렇게 원인 쪽을 먼저 정한 뒤 전용 도구로 갑니다(6장).5
- 프로세스가 아니라 OS 전체 메모리가 수상하면 RAMMap입니다.6 크래시면 ProcDump로 덤프 채취(「Windows 크래시 덤프 수집 입문」)로 전환합니다.
- 어느 도구든 설치가 필요 없고, Sysinternals Live에서 바로 실행할 수도 있습니다. 오프라인 장비 PC에도 넣기 쉬운 반면, 심볼만은 사전 준비가 필요합니다(8장).7
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 20건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. Process Explorer 기본 ── 관리자 실행·작업 관리자 대체·화면 읽는 법
Process Explorer는 설치가 필요 없고, ZIP을 풀어 procexp.exe(64bit면 자동으로 procexp64가 동작합니다)를 실행하면 됩니다.1 다른 사용자 프로세스나 서비스 안까지 보는 도구이므로, 조사할 때는 반드시 「관리자 권한으로 실행」합니다. 일반 권한으로 켜면 정작 봐야 할 프로세스에서만 「액세스가 거부되었습니다」가 나와 스택도 핸들도 보이지 않습니다.
조사 담당 PC에서는 Options → Replace Task Manager를 켜 두면 Ctrl+Shift+Esc나 작업 표시줄에서 작업 관리자를 열 때 그대로 Process Explorer가 뜹니다. 「급할 때 익숙한 도구가 열린다」는 효과는 작지 않아, 당사에서도 개발 PC는 전부 바꿔 두었습니다(원래대로 돌리는 것도 같은 메뉴입니다).
화면은 위가 프로세스 트리, 아래가 선택한 프로세스 상세인 2페인 구성입니다. 아래 페인은 View → Lower Pane View로 Handles 뷰(연 핸들 목록)와 DLLs 뷰(로드한 DLL과 메모리 맵 파일 목록)를 바꿉니다.1 먼저 익혀야 할 것은 행 색의 의미입니다(Options → Configure Colors에서 확인·변경할 수 있습니다).
| 색(기본) | 의미 |
|---|---|
| 초록 | 새로 시작한 프로세스(기본 약 1초간) |
| 빨강 | 종료 중인 프로세스 |
| 연한 파랑 | 나와 같은 사용자 계정의 프로세스 |
| 분홍 | 서비스를 호스트하는 프로세스 |
| 보라 | 패킹된(압축·난독화된) 실행 파일 의심 |
| 진한 회색 | 일시 중단된 프로세스 |
「잠깐 초록 프로세스가 생겼다가 바로 빨개지며 사라진다」가 끝없이 반복되는 식의 이상은 트리 표시와 색만으로도 찾습니다. 프로세스 부모·자식이 트리로 보이므로 「이 conhost.exe는 누구의 자식인가」「앱을 띄운 것은 서비스인가 작업 스케줄러인가」도 한눈에 알 수 있습니다.
3. 「파일이 사용 중이라 지울 수 없다」── Find Handle or DLL로 범인을 찾는다
「로그 파일을 삭제할 수 없다」「업데이트하려니 exe가 사용 중이다」「USB 메모리를 안전하게 제거할 수 없다」── 이 증상 조사는 Find → Find Handle or DLL(Ctrl+F)로 끝납니다.1 조작은 다음 5단계입니다.
- Process Explorer를 관리자 권한으로 실행한다(이걸 빠뜨리면 잡고 있는 쪽이 서비스나 다른 사용자 프로세스일 때 보이지 않습니다)
- 메뉴 Find > Find Handle or DLL…(바로 가기 Ctrl+F)를 연다
- 검색 문자열 칸에 파일 이름 또는 폴더 이름의 일부를 넣는다(예:
report.csv,D:\Data). 전체 경로일 필요는 없고, 부분 일치로 검색됩니다 - Search를 누른다. 그 이름이 들어간 핸들을 연 프로세스, 또는 그 DLL을 로드한 프로세스가 목록에 나옵니다
- 결과 행을 클릭하면 위 페인에서 해당 프로세스가 선택되고, 아래 페인에서 해당 핸들이 강조됩니다
찾은 뒤의 대응은 「그 프로세스를 정상 종료하는 것」이 올바른 방법입니다. Process Explorer에서 핸들을 오른쪽 클릭해 Close Handle로 강제 닫을 수도 있지만, 뒤에 나올 handle -c와 같은 이유로 운영에서는 권하지 않습니다.
이 검색의 한계도 먼저 짚어두세요. 적중하는 것은 이름이 있는 객체뿐입니다. 이름 없는 이벤트나 뮤텍스, 스레드 핸들은 아무리 많이 열려 있어도 이름으로는 검색할 수 없습니다. 「파일을 지울 수 없다」처럼 경로라는 이름이 있는 조사에서는 가장 강하지만, 5장의 핸들 누수 조사에서는 이 방법은 쓸 수 없고, 종류별 집계(handle -s)와 아래 페인 Handles 뷰를 Type 순으로 정렬하는 쪽으로 바꿔야 합니다(7장 사례가 그 실예입니다).
DLLs 뷰는 「어느 위치의 어느 버전 DLL을 실제로 로드했는지」 확인에도 씁니다. 「오래된 DLL을 잡고 있었다」「배치했을 수정본이 읽히지 않았다」 같은 DLL 버전 문제 확인은, 이 화면이 전편 ProcMon 글의 5.2절 「이 환경에서만 시작되지 않는다 ── DLL 탐색을 쫓는다」와 짝입니다. 그쪽은 「탐색 순으로 NAME NOT FOUND가 늘어서고, 어디서 SUCCESS하는지(또는 끝까지 못 찾는지)」를 시계열로 쫓는 절이라 어디를 찾아봤는지가 보입니다. 이쪽 DLLs 뷰에서는 결국 어디의 무엇을 잡았는지가 보입니다. 자세한 내용은 「Process Monitor(ProcMon) 실전 가이드」를 참조하세요.
애초에 「사용 중인 exe/DLL을 어떻게 안전하게 교체할지」는 조사가 아니라 설계 문제입니다. 같은 날 공개한 자매 글 「사용 중인 exe/DLL을 어떻게 교체할지」에서 Restart Manager를 포함한 교체 설계를 다루므로, 업데이트 기구를 만드는 쪽은 그쪽으로 가세요.
4. 「hang이 걸렸다」── Threads 탭에서 스레드 스택을 읽는다
창이 하얘지고 응답이 없습니다. 강제 종료하기 전에 Threads 탭에서 스레드 스택을 보세요. 조작은 다음 4단계입니다.
- 위 페인에서 대상 프로세스를 더블클릭한다(또는 오른쪽 클릭 → Properties…). 속성 창이 열립니다
- Threads 탭으로 전환한다. 스레드별 CPU 사용률·사이클 수·시작 주소가 늘어섭니다
- 볼 스레드를 고른다. CPU를 계속 쓰는 스레드, 또는 시작 주소가 앱 본체 EXE인 스레드(많은 경우 UI 스레드)가 첫 후보입니다
- Stack 버튼을 누른다. 그 스레드가 지금 어느 함수 호출 도중인지, 호출이 새로운 쪽부터 순서대로 표시됩니다
hang의 대부분은 「UI 스레드가 무언가를 기다린다」이므로, 스택 위쪽에 WaitForSingleObject나 EnterCriticalSection, 동기 네트워크·시리얼 I/O 대기가 보이면 그것이 직접 원인입니다.
다만 심볼을 설정하지 않으면 스택은 주소와 모듈이름+0x1234 나열이 되어 거의 읽을 수 없습니다. Options → Configure Symbols에서 다음 둘을 설정합니다.
Dbghelp.dll path:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\dbghelp.dll
※ WinDbg(Debugging Tools for Windows)에 들어 있는 것을 지정한다
Symbols path:
srv*C:\Symbols*https://msdl.microsoft.com/download/symbols
※ C:\Symbols 는 로컬 캐시. 두 번째부터는 여기서 읽힌다
요점은 둘입니다. 먼저 System32의 기본 dbghelp.dll은 심볼 서버 다운로드를 지원하지 않으므로 WinDbg 부속본을 가리켜야 한다는 것. 그리고 공식 문서대로 심볼 서버를 쓸 때는 지정한 dbghelp.dll과 같은 위치에 symsrv.dll이 있어야 한다는 것입니다(WinDbg 설치 폴더면 둘 다 있습니다).1 심볼 경로의 srv*캐시*서버URL 형식과 Microsoft 공개 심볼 서버 https://msdl.microsoft.com/download/symbols는 WinDbg와 같은 메커니즘입니다.3 자사 앱 스택을 읽으려면 자사 빌드 PDB도 필요하므로, 심볼 경로에 PDB 두는 폴더를 세미콜론으로 구분해 더해 둡니다.
그 dbghelp.dll을 어디서 가져올지가 여기서 맨 먼저 막히는 지점입니다. 위 경로의 C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\는 Debugging Tools for Windows를 넣으면 생기는 폴더입니다. 입수 경로는 세 가지입니다.8
| 입수 경로 | 맞는 장면 |
|---|---|
| Windows SDK 설치 관리자에서 「Debugging Tools for Windows」만 고른다 | 이번 용도에는 이것이 가장 빠릅니다. 다른 기능 체크를 모두 끄고 실행하면 SDK 본체 없이 디버거만 들어갑니다8 |
| Windows SDK / WDK의 일부로 넣는다 | 개발 PC에서 다른 용도로도 SDK가 필요할 때 |
WinDbg 단독 설치(winget install Microsoft.WinDbg 또는 Microsoft Store) |
WinDbg 본체를 쓰고 싶을 때. 다만 패키지 형식이라 위와 같은 고정 경로는 되지 않습니다9 |
Process Explorer에 넣는 것은 dbghelp.dll의 파일 경로이므로, 설치 후 그 폴더 안에 dbghelp.dll과 symsrv.dll이 나란히 있는지 확인한 뒤 지정하세요.
.NET 앱이면 Threads 탭 스택은 네이티브 프레임 중심이라 매니지드 메서드 이름이 정확히 나오지 않을 수 있습니다. 매니지드 hang·교착을 본격적으로 쫓으려면 hang 중에 덤프를 떠 WinDbg + SOS로 보는 편이 확실합니다(「WinDbg + SOS로 크래시 덤프 읽기」). Threads 탭은 「그 자리에서 3분 안에 감을 잡는」 도구, 덤프는 「가져가 확정하는」 도구라는 역할 분담입니다. 참고로 Threads 탭의 Kill/Suspend 버튼은 운영 프로세스에는 누르지 마세요. 관찰 의도가 개입이 됩니다.
5. 「점점 무거워진다」── 핸들 수·USER/GDI 컬럼과 handle.exe로 누수를 감시한다
장기 가동으로 나빠지는 앱의 단골 원인이 핸들·GDI/USER 객체·메모리 누수입니다. Process Explorer는 감시 도구로도 뛰어나며, View → Select Columns에서 다음 컬럼을 추가합니다.
- Process Performance 탭 → Handle Count(커널 객체 핸들 수)
- Process Memory 탭 → Private Bytes, Virtual Size, USER Objects, GDI Objects
보는 것은 절댓값이 아니라 기울기입니다. 건전한 앱은 핸들 수가 조작에 따라 늘었다 줄었다 하면서 일정 범위에 머물지만, 누수면 「조작할 때마다 늘고, 다시는 줄지 않는」 우상향이 됩니다. 1시간마다, 또는 아침저녁으로 스크린샷만 남겨도 다음날이면 경향이 보입니다. GDI 객체와 USER 객체에는 프로세스별 상한(기본 1만 개, 레지스트리 GDIProcessHandleQuota 등으로 변경 가능)과 세션 전체 이론 상한 65,536개가 있어, 상한에 닿으면 펜·브러시 생성, 창 생성이 실패하기 시작합니다.4 「금요일이 되면 화면 그리기가 깨진다」의 정체는 대개 이것입니다.
GUI를 열 수 없는 환경이나 스크립트 정기 관측에는 CLI 버전 handle.exe를 씁니다. 핸들 목록·검색을 하는 명령줄 도구이며 관리자 권한이 필요합니다.2
:: 이 파일/폴더를 누가 잡고 있는지(이름 부분 일치 검색)
handle.exe /accepteula report.csv
handle.exe D:\Data
:: 파일 외를 포함한 모든 종류 핸들을 프로세스 이름으로 좁혀 덤프
handle.exe -a -p MyEquipApp
:: 종류별 핸들 수 집계 ── 누수 정기 관측은 이것을 로그에 남긴다
handle.exe -s -p MyEquipApp.exe >> C:\Logs\handles_%date:~0,4%%date:~5,2%%date:~8,2%.log
3행째 %date:~0,4%에 대해 보충합니다. 이것은 환경 변수의 부분 문자열 확장이며, 배치 파일 안이든 명령 프롬프트에 직접 치든 표기는 % 하나입니다(%%로 두 겹이 필요한 것은 for 변수와, 리터럴 %를 쓰고 싶을 때이며, 이 표기는 해당하지 않습니다).
다만 %date% 내용은 OS의 짧은 날짜 형식에 의존하므로 ~0,4(앞 4글자=연도) 같은 오프셋이 환경마다 어긋납니다. 일본어판 Windows 기본은 yyyy/MM/dd라 위 예가 맞지만, 장비 PC 지역 설정이 다르면 파일 이름이 깨진 형태로 만들어집니다. 환경을 건너 배포하는 정기 관측 스크립트라면 날짜 생성만은 형식이 확정되는 쪽으로 맞추세요.
:: 환경의 날짜 형식에 의존하지 않는 표기(배치 파일에 쓸 때)
for /f %%d in ('powershell -NoProfile -Command "Get-Date -Format yyyyMMdd"') do set TODAY=%%d
handle.exe -s -p MyEquipApp.exe >> C:\Logs\handles_%TODAY%.log
for 변수는 배치 파일 안에서는 %%d, 명령 프롬프트에 직접 칠 때는 %d로 구분해 써야 합니다.10 위는 배치 파일용이므로 그대로 명령 프롬프트에 붙이면 동작하지 않습니다. 직접 시험할 때는 %%d를 %d로 바꾸세요. 정기 관측은 작업 스케줄러에서 돌리게 되므로 실제 운영에서는 배치 파일 쪽 표기를 씁니다. 여기가 앞머리 %date:~0,4%(어느 쪽이든 % 하나)와 섞이기 쉬운 지점입니다.
-s 출력(Event: 1523, File: 88, … 같은 종류별 집계)을 작업 스케줄러로 1시간마다 이어 쓰면, 원격 유지보수만 가능한 장비 PC에서도 「어느 종류 핸들이 하루에 몇 개 늘는지」를 숫자로 잡을 수 있습니다. 종류가 보이면 용의자는 한 번에 좁혀집니다. Event면 이벤트 해제 누락, File이면 닫기 누락, Thread면 스레드 종료 후 핸들 방치, 식입니다.
참고로 -c 옵션으로 지정 핸들을 강제 닫을 수 있지만, 공식 문서 자신이 「핸들을 닫으면 애플리케이션이나 시스템이 불안정해질 수 있다」고 경고합니다.2 잠금 해제의 응급 처치로도 운영에서는 기본적으로 쓰지 않는 것이 당사 방침입니다. 「어느 종류가 누수인지」까지 안 뒤 「어느 코드가 할당했는지」를 특정하는 절차는 「산업용 카메라 장기 가동 크래시 조사 - 핸들 누수편」과 「Application Verifier로 비정상 계열 테스트의 토대를 만든다」로 이어집니다.
6. VMMap ── 「Private Bytes가 계속 늘면」 내역을 나눈다
핸들은 거의 그대로인데 Private Bytes만 계속 는다 ── 그때 여는 것이 VMMap입니다. 프로세스의 가상 메모리와 물리 메모리(워킹 세트)를 분석하는 도구로, 커밋된 가상 메모리를 종류별로 분해해 보여 줍니다.5 실행해 대상 프로세스를 고르면 위쪽에 색이 나뉜 요약, 아래쪽에 상세 메모리 맵이 나옵니다. 주요 분류는 이렇게 읽습니다.
| 분류 | 내용 | 계속 늘 때의 전형적인 범인 |
|---|---|---|
| Image | EXE/DLL 본체 | 플러그인 DLL을 로드한 채 방치 |
| Heap | 네이티브 힙(new/malloc/HeapAlloc) | C/C++ 코드, 장비 SDK, 상호운용 해제 누락 |
| Managed Heap | .NET GC 힙 | 매니지드 참조 누수(이벤트 핸들러 등) |
| Private Data | VirtualAlloc 직접 할당 등 | 이미지 프레임 버퍼, SDK 내부 버퍼 |
| Stack | 스레드 스택 | 스레드를 만든 채 방치 |
| Shareable / Mapped File | 공유 메모리·맵된 파일 | 프로세스 간 연동의 섹션 해제 누락 |
조작은 다음 4단계입니다.
- VMMap을 관리자 권한으로 실행한다. 시작 직후 프로세스 선택 대화상자가 나오므로 대상 프로세스를 고르고 OK를 누른다
- 위쪽 요약 표를 본다. 행이 위 표의 Type(Image / Heap / Managed Heap / Private Data / Stack …), 열이 Size나 Committed 같은 크기입니다. 이 시점의 값을 메모하거나 File 메뉴에서 내보내기해 남겨 두는 것이 핵심입니다
- 문제의 조작(장비 측정 1사이클, 화면 열고 닫기 등)을 정한 횟수만큼 반복한다
- F5(Refresh)로 스냅샷을 갱신하고 2에서 남겨 둔 값과 비교한다. 늘어난 Type 행을 클릭하면 아래쪽에 그 영역 목록이 나옵니다
사용 패턴은 「F5로 스냅샷을 갱신하면서 문제 조작을 반복해, 어느 분류가 늘는지 본다」입니다. VMMap은 스냅샷 비교와 타임라인 표시, 결과 내보내기에 대응하므로5 「측정 100회에 Heap이 40MB 증가, Managed Heap은 거의 그대로」 같은 증거가 그 자리에서 잡힙니다. 여기서 원인 쪽이 정해지면 다음은 전용 도구 차례입니다. Managed Heap이면 .NET 쪽 조사(「PerfView와 dotnet-trace로 「느림」을 특정한다」), Heap/Private Data면 네이티브 쪽 조사(Application Verifier나 덤프 분석)로 갑니다. 이 구분을 하지 않고 바로 GC를 의심해 며칠을 허비하는 것이 .NET+네이티브 SDK 구성의 장비 앱에서 가장 흔한 우회로입니다.
32bit 프로세스에서는 단편화도 관찰 대상입니다. Fragmentation View에서 주소 공간 빈 자리의 흩어짐이 보이고, Free 합계는 수백 MB인데 최대 연속 빈 자리(Largest)는 수십 MB뿐인 상태가 시각화됩니다. 「메모리는 비어 있을 텐데 OutOfMemory」「큰 이미지 버퍼 할당만 실패한다」의 정체는 대개 이것이고, 대책(64bit화, 버퍼 재사용 설계)의 근거 자료가 됩니다.
프로세스 단위가 아니라 OS 전체 메모리가 수상할 때(어느 프로세스도 커지지 않았는데 메모리가 부족해질 때)는 물리 메모리 전체 용도를 탭별로 보여 주는 RAMMap으로 바꿉니다. 파일 캐시·드라이버·커널 사용량까지 보이므로 「앱 이외의 범인」은 여기서 찾습니다.6
7. 케이스 스터디 ── 「매주 금요일에 무거워지는 장비 PC」 원인을 가른다
맨 앞의 장비 PC를 여기까지의 도구로 실제로 나누면 이렇게 됩니다. 월요일 아침에 재부팅하는 운영이므로 금요일은 「가동 5일째」입니다. 즉 시간에 비례해 늘어나는 무언가가 있다가 초기 가설입니다.
- 수요일쯤 Process Explorer를 두고 컬럼을 넣는다. Handle Count·USER Objects·GDI Objects·Private Bytes를 추가하고 대상 프로세스 값을 기록합니다. 아울러
handle -s -p 대상앱.exe의 매시 로그를 작업 스케줄러에 등록합니다(5장). - 다음날 기울기를 본다. 이 사례에서는 Handle Count가 하루에 약 2만 증가, 종류별 로그에서는 Event 핸들이 단조 증가했습니다. Private Bytes와 GDI/USER는 거의 그대로입니다. 이 시점에 「커널 객체(이벤트) 해제 누락」으로 좁혀집니다.
- Handles 뷰에서 실물을 본다. 아래 페인 Handles 뷰를 Type 순으로 정렬하면 이름 없는 Event가 수만 개. 이름이 없어 Find Handle로는 쫓을 수 없지만, 종류와 증가 속도가 보이면 충분합니다. 측정 주기(이 사례에서는 장비 폴링 1회당 2개)와의 대응에서 통신 라이브러리 대기 이벤트 해제 누락이라는 가설이 서고, 코드 리뷰로 확정했습니다. 할당 지점을 도구로 특정한다면 여기서부터 Application Verifier나 !htrace 차례입니다.
- 만약 Private Bytes가 늘고 있었다면 3 대신 VMMap으로 내역을 나누고(6장), Heap이면 네이티브, Managed Heap이면 .NET 조사로 나뉩니다.
- 만약 어느 숫자도 거의 그대로인데 hang만 난다면 Threads 탭에서 스택을 보고(4장) 대기처를 특정합니다.
핵심은 수정 전에 「그래프 기울기」라는 증거를 확보해 두는 것입니다. 수정 후 같은 측정을 해 기울기가 0이 된 것을 보이면 「고쳐졌다」를 숫자로 보고할 수 있습니다. 재현 대기가 며칠 단위인 장기 가동 문제에서는 이 측정 준비가 조사의 본체입니다.
8. 운영 PC 반입과 운영 시 주의 ── Sysinternals Live·EULA·오프라인 심볼
Sysinternals 도구는 모두 설치가 필요 없는 독립 실행 파일이며, ZIP을 USB 메모리로 가져가면 그대로 동작합니다. 최초 실행 시 EULA 동의 대화상자가 뜨므로, 무인 실행이나 스크립트에서는 /accepteula 스위치로 동의를 명시합니다. 네트워크를 쓸 수 있는 환경이라면 Sysinternals Live 서비스로 내려받지 않고 https://live.sysinternals.com/procexp.exe 같은 URL이나 \\live.sysinternals.com\tools\<도구이름> UNC 경로에서 바로 실행할 수 있습니다.7 「지금 당장 이 한 대에서 보고 싶다」일 때의 가장 짧은 경로입니다.
오프라인 장비 PC에서 걸리는 것은 심볼입니다(4장 스택 표시를 쓸 수 없음). 대응은 둘이며, (1) 인터넷에 연결된 개발 PC에서 같은 OS 빌드에 대해 심볼을 해석시키고, 로컬 캐시(C:\Symbols)째로 장비 PC에 복사해 심볼 경로를 로컬 폴더만으로 둔다. (2) 스택 분석은 포기하고, 현장에서는 덤프 채취(ProcDump)와 수치 기록에 그친 뒤 개발 PC로 가져와 분석한다. 확실한 것은 (2)이며, 덤프 뜨는 법은 「Windows 크래시 덤프 수집 입문」에 정리되어 있습니다.
마지막으로 운영 면입니다. Process Explorer나 VMMap에 의한 관찰은 부하·위험이 낮지만, 프로세스 Kill/Suspend, 스레드 Kill, Close Handle, handle -c 같은 개입 조작은 운영에서 원칙 금지로 하세요. 전편 ProcMon과 같이, 실시는 평소 변경 작업과 같은 승인 절차에 올리고 「언제·누가·무엇을 관찰하고, 무엇을 조작하지 않을지」를 정한 뒤 임하는 것이 안전합니다.
9. 실무의 정석(판단표)
| 증상 | 쓰는 도구 | 보는 곳 |
|---|---|---|
| 점점 무거워진다·며칠 만에 불안정해진다 | Process Explorer | Handle Count / USER·GDI Objects / Private Bytes 컬럼의 기울기(5장) |
| 파일을 삭제·교체할 수 없다 | Process Explorer / handle.exe | Find Handle or DLL(Ctrl+F)로 경로 검색 → 잡고 있는 프로세스(3장) |
| hang·응답 없음 | Process Explorer | 속성 → Threads 탭 → 스레드 스택(심볼 필요, 4장) |
| Private Bytes가 계속 는다 | VMMap | Heap / Managed Heap / Private Data 중 어느 것이 느는지(6장) |
| 어느 프로세스도 커지지 않았는데 메모리 부족 | RAMMap | Use Counts·File Summary로 물리 메모리 용도6 |
| 크래시한다·예외로 떨어진다 | ProcDump + WinDbg | 덤프 수집 → WinDbg + SOS |
| CPU를 어디서 쓰는지·.NET GC | PerfView / dotnet-trace | 「느림」의 정량 조사 |
| 조작의 시계열(어느 파일을·언제·어느 순으로) | Process Monitor | 전편 글 |
10. 정리
- ProcMon이 「조작 이력」을 기록하는 데 비해 Process Explorer / Handle / VMMap은 「지금 이 순간의 상태」를 보는 도구입니다. 장기 가동 앱 조사에는 둘 다 필요합니다.
- 「파일을 지울 수 없다」는 Find Handle or DLL(Ctrl+F)로 몇 초, 「hang이 걸렸다」는 Threads 탭 스택으로 감을 잡습니다. 스택을 읽으려면 dbghelp.dll(symsrv.dll이 같이 있는 위치)과 심볼 경로 설정이 전제이며, 그 dbghelp.dll은 Debugging Tools for Windows를 넣어 구합니다.
- 이름으로 검색할 수 있는 것은 이름이 있는 객체뿐입니다. 파일처럼 이름이 있는 것에는 가장 강하지만, 이름 없는 이벤트나 뮤텍스는 검색에 걸리지 않습니다. 핸들 누수 조사를 「Ctrl+F로 범인을 찾는다」부터 시작하면 헛수고이므로, 종류별 집계(
handle -s)와 Handles 뷰의 Type 순 정렬로 바꾸세요. - 「점점 무거워진다」는 Handle Count·USER/GDI Objects·Private Bytes 컬럼을 추가해 기울기를 봅니다. handle -s 정기 로그면 GUI를 열 수 없는 장비 PC에서도 숫자가 나옵니다.
- Private Bytes 증가는 VMMap으로 Heap(네이티브)/ Managed Heap(.NET)/ Private Data로 나눈 뒤 전용 도구로 갑니다. 32bit 프로세스면 단편화도 의심합니다.
- handle -c나 Close Handle로 강제 닫기는 공식이 경고하듯 불안정화의 원인입니다. 운영에서는 관찰에 그치고, 개입은 하지 않거나 한다면 승인을 통과시키는 것을 철저히 하세요.
- 도구는 독립 실행이라 반입하기 쉽고, Sysinternals Live면 바로 실행할 수도 있습니다. 오프라인 환경에서는 심볼 사전 캐시 또는 덤프를 가져와 보완합니다.
관련 글
- Process Monitor(ProcMon) 실전 가이드 ── 「설정이 읽히지 않는다」「ACCESS DENIED」를 10분 만에 특정한다
- 사용 중인 exe/DLL을 어떻게 교체할지
- 산업용 카메라 장기 가동 크래시 조사 - 핸들 누수편
- Application Verifier로 비정상 계열 테스트의 토대를 만든다
- Windows 크래시 덤프 수집 입문 - WER/ProcDump/WinDbg
- WinDbg + SOS로 크래시 덤프 읽기
- PerfView와 dotnet-trace로 「느림」을 특정한다
관련 상담 영역
合同会社小村ソフト에서는 장기 가동으로 나빠지는 업무 앱·장비 연동 앱의 누수 조사, hang·「파일 사용 중」 같은 현장 장애의 원인 분석, 증거 채취 절차 정비와 재발 방지 개수를 다룹니다.
참고 링크
-
Microsoft Learn, Process Explorer - Sysinternals. Process Explorer가 프로세스가 연 핸들과 로드한 DLL·메모리 맵 파일을 2페인(handle 모드/DLL 모드)으로 표시하는 것, 특정 핸들이나 DLL을 가진 프로세스를 검색할 수 있는 것, DLL 버전 문제나 핸들 누수 추적에 유용한 것, 심볼 서버 이용 시 dbghelp.dll과 같은 위치에 symsrv.dll이 필요한 것, Sysinternals Live에서 바로 실행할 수 있는 것, 동작 요건이 클라이언트는 Windows 11 이상·서버는 Windows Server 2016 이상인 것에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7
-
Microsoft Learn, Handle - Sysinternals. handle.exe가 연 핸들 정보를 표시하는 명령줄 도구이며 관리자 권한이 필요한 것, 이름 부분 일치 검색, -a로 모든 종류 핸들을 대상으로 하는 것, -p로 프로세스 좁히기, -s로 종류별 핸들 수 집계, -c로 핸들을 닫을 수 있으나 「애플리케이션이나 시스템 불안정화를 부를 수 있다」고 경고되는 것에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Microsoft Public Symbol Server. Microsoft 공개 심볼 서버의 심볼 경로를 srv로컬캐시https://msdl.microsoft.com/download/symbols 형식으로 지정할 수 있는 것, 다운스트림 스토어(로컬 캐시)에는 한 번 접근한 심볼만 저장되고 두 번째부터는 로컬에서 읽는 것에 대해. ↩ ↩2
-
Microsoft Learn, GDI Objects. GDI 핸들에 세션당 이론 상한 65,536개가 있는 것, 프로세스별 기본 상한이 존재하며 레지스트리 GDIProcessHandleQuota(256〜65,536 범위)로 변경할 수 있는 것에 대해. ↩ ↩2
-
Microsoft Learn, VMMap - Sysinternals. VMMap이 프로세스의 가상·물리 메모리 분석 도구이며, 커밋된 가상 메모리의 종류별 내역과 각 종류에 할당된 물리 메모리(워킹 세트)를 표시하는 것, 필터와 갱신(스냅샷) 기능, 데이터 내보내기와 명령줄 옵션으로 스크립트화에 대응하는 것에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, RAMMap - Sysinternals. RAMMap이 OS 전체 물리 메모리 사용 상황을 분석하는 도구이며, Use Counts(종류별 집계), Processes(프로세스 워킹 세트), File Summary(RAM상의 파일 데이터) 등 탭으로 용도를 표시하는 것, 스냅샷 저장·읽기에 대응하는 것에 대해. ↩ ↩2 ↩3
-
Microsoft Learn, Sysinternals. Sysinternals Live가 도구를 내려받지 않고 실행할 수 있는 서비스이며, live.sysinternals.com/<도구이름> URL 또는 \\live.sysinternals.com\tools\<도구이름> 경로로 바로 실행할 수 있는 것, live.sysinternals.com에서 도구 목록을 볼 수 있는 것에 대해.도구이름>도구이름> ↩ ↩2
-
Microsoft Learn, Debugging Tools for Windows SDK and WDK. Debugging Tools for Windows가 Windows SDK 및 WDK에 포함되는 것, Windows SDK 설치 관리자를 실행해 기능 목록에서 「Debugging Tools for Windows」만 고르고 나머지를 모두 끄면 디버거만 단독 도입할 수 있는 것, 설치 관리자 입수처가 Windows SDK인 것에 대해. ↩ ↩2
-
Microsoft Learn, Install WinDbg. WinDbg가 크래시 덤프 분석이나 사용자 모드·커널 모드 디버깅에 쓸 수 있는 것, 직접 설치 관리자를 실행하는 방법·Microsoft Store 경유·
winget install Microsoft.WinDbg로 도입하는 방법이 있는 것, 기존의 WinDbg(classic)는 Debugging Tools for Windows에 포함되는 것에 대해. ↩ -
Microsoft Learn, for. 배치 파일 안에서는
%%variable, 명령 프롬프트에서 직접 실행할 때는%variable로 쓰는 것, 및for /f가 명령 출력을 해석할 수 있는 것에 대해. ↩
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
Windows의 「메모리 사용량」은 무엇을 나타내는가 ── Working Set·Private Bytes·Commit·페이지 파일을 올바르게 읽는 법
작업 관리자의 메모리, Working Set, Private Bytes, Commit은 같은 값이 아닙니다. Windows의 가상 메모리와 물리 메모리의 관계, 페이지 파일의 역할, 메모리 부족이나 누수 조사에서 봐야 할 지표를 설명합니다.
Windows의 이름 확인 순서 ── hosts·DNS 캐시·LLMNR/mDNS·DoH
「이름을 확인할 수 없다」「일부 PC만 연결되지 않는다」는 hosts·DNS 캐시·DNS 서버·LLMNR/mDNS 중 어느 층이 답했는지에 따라 결과가 달라집니다. Windows의 이름 확인 순서와 DoH가 바꾸는 것을 구조부터 정리하고, 층별로...
절전에서 재개하면 깨지는 앱 ── 전원 이벤트의 구조와 재개에 강한 업무 앱을 만드는 방법
노트북을 열었더니 업무 앱의 통신이 끊어져 있었다――원인은 절전을 전제하지 않은 설계입니다. 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 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
장애 조사 & 장기 가동 장애
간헐적 장애, 통신 진단, 장기 가동 크래시, 실패 경로 테스트 기반을 정리한 토픽 페이지입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
장애 조사 & 원인 분석
간헐적 장애, 장기 가동 중 크래시, 누수, 통신 중단 등 까다로운 프로덕션 이슈를 조사합니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- 작업 관리자가 있는데 굳이 Process Explorer를 쓰는 이유는 무엇인가요?
- 작업 관리자로는 「어느 프로세스가 어떤 파일이나 객체를 열고 있는지」「각 스레드가 지금 어느 코드에서 멈춰 있는지」가 보이지 않기 때문입니다. Process Explorer는 프로세스별 핸들·DLL 목록, 이름으로 핸들 검색(Ctrl+F), 스레드 스택 표시까지 되므로, 「파일을 지울 수 없다」「hang이 걸렸다」 조사가 작업 관리자와는 차원이 달라집니다. Handle Count나 USER/GDI 객체 수 컬럼을 추가하면 누수 감시 도구로도 쓸 수 있습니다. Options 메뉴의 Replace Task Manager를 켜면 작업 관리자 호출이 Process Explorer로 바뀌므로, 조사 담당 PC에서는 바꿔 두는 편이 실무적입니다.
- handle.exe의 -c 옵션으로 핸들을 닫으면 파일 잠금을 바로 해제할 수 있나요?
- 기술적으로는 가능하지만, 운영 환경에서는 기본적으로 쓰지 마세요. 공식 문서 자체가 「핸들을 닫으면 애플리케이션이나 시스템이 불안정해질 수 있다」고 경고합니다. 프로세스의 내부 상태를 무시한 채 밖에서 핸들을 빼앗기 때문에, 그 프로세스가 나중에 같은 핸들을 쓰는 순간 다른 객체를 가리키고 있어 오동작하는(핸들 값 재사용) 최악의 사고도 날 수 있습니다. 올바른 방법은 잡고 있는 프로세스를 찾아 정상 종료하는 것입니다. 사용 중인 exe나 DLL을 교체하려는 맥락이라면 Restart Manager 같은 설계 쪽 해결을 검토하세요.
- Private Bytes가 계속 늘고 있습니다. 맨 먼저 무엇을 해야 하나요?
- 대상 프로세스를 VMMap으로 열어, 늘고 있는 것이 어느 분류인지 보는 것이 첫 단계입니다. Heap이 늘면 malloc/new/HeapAlloc에 의한 네이티브 누수 가능성이 크고, 장비 벤더 SDK나 C++/CLI·상호운용 코드가 용의자가 됩니다. Managed Heap이 늘면 .NET 매니지드 힙 문제이므로, GC가 회수하지 못하는 참조 조사(이벤트 핸들러 해제 누락 등)로 넘어갑니다. Private Data(VirtualAlloc 직접 할당)가 늘면 이미지 버퍼처럼 큰 블록을 잡는 코드나 SDK를 의심합니다. 구분이 끝난 뒤에 각각 전용 도구(WinDbg, PerfView 등)로 가면 조사가 길을 잃지 않습니다.
- 오프라인 장비 PC에서는 Threads 탭의 스택이 주소 나열만 보여 읽을 수 없습니다. 어떻게 하면 되나요?
- 심볼(PDB)을 받을 수 없기 때문이며, 대응은 「심볼을 가져간다」 또는 「덤프를 가져온다」 둘 중 하나입니다. 가져갈 때는 인터넷에 연결된 개발 PC에서 같은 OS 빌드·같은 앱 구성으로 심볼을 한 번 해석시킨 뒤, 로컬 캐시 폴더(예: C:\Symbols)째로 장비 PC에 복사하고 심볼 경로를 그 로컬 폴더만으로 둡니다. 가져올 때는 hang 중에 덤프를 떠서 개발 PC의 WinDbg로 분석하는 편이 확실하고, 매니지드 앱 스택을 정확히 읽고 싶을 때도 이쪽이 맞습니다. 자사 앱 PDB는 빌드마다 보관해 두는 것이 대전제입니다.
- Process Explorer나 VMMap을 운영 PC·장비 PC에 넣어도 문제없나요?
- 둘 다 설치가 필요 없는 독립 실행 파일이며, 레지스트리 등록이나 서비스 상주 없이 동작하므로 반입 부담이 낮은 도구입니다. 최초 실행 시 EULA 동의가 필요하고, 스크립트에서 쓸 때는 /accepteula 스위치로 동의를 명시할 수 있습니다. 네트워크를 쓸 수 있는 환경이라면 Sysinternals Live(https://live.sysinternals.com)에서 내려받지 않고 바로 실행할 수도 있습니다. 다만 관찰은 위험이 낮아도, 프로세스 Kill/Suspend, 스레드 Kill, 핸들 강제 닫기 같은 「개입」 조작은 사고로 직결되므로, 운영에서는 관찰과 기록에 그치고 평소 작업 승인 절차를 거쳐 실시하세요.