리눅스 메모리 회계 — RSS·VSS·PSS·smaps 해석
“이 프로세스가 메모리를 얼마나 쓰나?” 답이 RSS, VSS, PSS, USS 중 어느 것이냐에 따라 4가지입니다. 잘못 답하면 OOM이 일어났을 때 왜인지 알 수 없고, 누수 디버깅도 헛수고. 이 시리즈는 메모리 진단의 처음부터 — 정확한 회계, 누수 도구, glibc/jemalloc/tcmalloc profiling까지 다룹니다.
#4가지 메모리 크기
// 프로세스가 100MB를 malloc → 그 중 80MB만 touch// 그리고 50MB의 .so를 다른 프로세스와 공유| 종류 | 의미 | 위 예시 값 |
|---|---|---|
| VSS (VSZ) | virtual size — 매핑된 모든 가상 메모리 | 100MB + 50MB = 150MB |
| RSS | resident set — 실제 메모리에 올라온 (page table 포함) | 80MB + 50MB = 130MB |
| PSS | proportional set — 공유 메모리를 공유 N분의 1로 | 80MB + 50/N |
| USS | unique set — 이 프로세스만의 (private) | 80MB |
OOM 시 합산해 OOM인지 보려면 PSS가 정확 (공유분 중복 계산 안 함). 누수 추적엔 USS 또는 PSS.
#출처 — /proc/[pid]/
$ cat /proc/12345/status | grep -E "Vm|Rss"VmPeak: 150000 kBVmSize: 140000 kB # = VSSVmLck: 0 kBVmPin: 0 kBVmHWM: 130000 kB # = RSS의 최고치VmRSS: 128000 kB # = RSS (현재)RssAnon: 105000 kB # heap, stack (private)RssFile: 20000 kB # mmap된 파일RssShmem: 3000 kB # 공유 메모리VmData: 120000 kBVmStk: 132 kBVmExe: 152 kBVmLib: 10000 kBVmPTE: 260 kB # page table 자체VmSwap: 2000 kB대부분 도구(top, ps, htop)가 VmRSS를 표시. 빠르지만 공유 메모리 중복 카운트.
#PSS — 정확한 회계
$ sudo cat /proc/12345/smaps_rollup55a9b3f000-7ffd8c5f6000 ---p 00000000 00:00 0 [rollup]Rss: 128000 kBPss: 120000 kB # ← 이게 더 정확Pss_Anon: 105000 kBPss_File: 12000 kB # 공유 .so의 비율 분Pss_Shmem: 3000 kBShared_Clean: 15000 kBShared_Dirty: 0 kBPrivate_Clean: 8000 kBPrivate_Dirty: 105000 kB # ← *내가 진짜 갖고 있는 변경된 메모리*Referenced: 125000 kBAnonymous: 105000 kBLazyFree: 0 kBAnonHugePages: 4096 kBSwap: 2000 kBSwapPss: 1800 kB핵심:
- PSS = “이 프로세스의 공정한 몫”. 모든 프로세스 PSS 합 = 시스템 메모리 사용.
- Private_Dirty = “이 프로세스만의 수정된 페이지”. heap 같은 누수 추적의 핵심.
# 모든 프로세스 PSS 정렬$ for pid in $(pgrep -f .); do pss=$(sudo cat /proc/$pid/smaps_rollup 2>/dev/null | grep "^Pss:" | awk '{print $2}') comm=$(cat /proc/$pid/comm 2>/dev/null) echo "$pss $pid $comm" done | sort -n -r | head -10#smaps — 영역별 분해
$ sudo cat /proc/12345/smaps | head -4055a9b3f00000-55a9b3f01000 r--p 00000000 fe:01 12345 /usr/local/bin/myprogSize: 4 kBKernelPageSize: 4 kBMMUPageSize: 4 kBRss: 4 kBPss: 4 kBShared_Clean: 0 kBShared_Dirty: 0 kBPrivate_Clean: 4 kBPrivate_Dirty: 0 kBReferenced: 4 kBAnonymous: 0 kBLazyFree: 0 kBAnonHugePages: 0 kBShmemPmdMapped: 0 kBFilePmdMapped: 0 kBShared_Hugetlb: 0 kBPrivate_Hugetlb: 0 kBSwap: 0 kBSwapPss: 0 kBLocked: 0 kBTHPeligible: 0ProtectionKey: 0VmFlags: rd mr mw me
55a9b3f01000-55a9b3f03000 r-xp 00001000 fe:01 12345 /usr/local/bin/myprog[Size, Rss, Pss, ...]...각 VMA(Virtual Memory Area)의 완전한 회계. 무엇이 메모리를 잡고 있는지 정확히.
VMA 종류:
r--p ... /path/to/exe— 실행 파일 .text (RO + private)rw-p ... [heap]— heap (mmap된 small allocs는 별개)rw-p ... [stack]— main 스레드 stackr--p ... /lib/libc.so.6— 공유 라이브러리 ROrw-p ...(anon) — anonymous mmap, malloc 큰 영역rw-s ... /dev/shm/...— POSIX shared memory
#도구 — pmap
$ pmap -X 12345 | head12345: /usr/local/bin/myprog Address Perm Offset Device Inode Size Rss Pss Referenced Anonymous LazyFree ShmemPmdMapped FilePmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked THPeligible Mapping 55a9b3f00000 r--p 00000000 fe:01 12345 4 4 4 4 0 0 0 0 0 0 0 0 0 0 myprog 55a9b3f01000 r-xp 00001000 fe:01 12345 128 128 128 128 0 0 0 0 0 0 0 0 0 0 myprog ... ========================= 140000 128000 120000smaps 와 같은 정보를 표 형식. -X 옵션이 모든 컬럼.
#큰 mmap 찾기
# 어떤 매핑이 크고 dirty인가$ sudo cat /proc/12345/smaps | awk ' /^[0-9a-f]+-/ { mapping=$0 } /^Private_Dirty:/ { if ($2 > 1024) print $2 " " mapping }' | sort -n -r | head운영 누수 진단의 빠른 방법 — 어느 영역에서 메모리 누수.
#RssAnon vs RssFile vs RssShmem
RssAnon: 105000 kB # heap + stack + anonymous mmapRssFile: 20000 kB # 파일 매핑 (실행 + 공유 라이브러리)RssShmem: 3000 kB # tmpfs / POSIX shared memory누수 진단의 핵심 구분:
- RssAnon 증가 → heap 또는 mmap 누수 (앱 코드 문제).
- RssFile 증가 → 매핑 후 unmap 안 함.
- RssShmem 증가 → /dev/shm 또는 SysV shm 누수.
#OOM Killer — 어느 프로세스가 죽을까
# OOM 점수 (높을수록 죽을 후보)$ cat /proc/12345/oom_score234
$ cat /proc/12345/oom_score_adj0 # -1000 ~ 1000, 사용자 조정 가능
# 우선 보호$ echo -1000 | sudo tee /proc/12345/oom_score_adj # 절대 안 죽음oom_score는 RSS + 일부 휴리스틱. PSS 아님 — 공유 메모리 많은 프로세스가 부풀어 보임.
#OOM 로그 — dmesg
Out of memory: Killed process 12345 (myprog) total-vm:8000000kB, anon-rss:7000000kB, file-rss:50000kB, shmem-rss:0kB[oom selection scoring][memory state at OOM time]total-vm = VSS, anon-rss + file-rss + shmem-rss = RSS 분해. 어느 종류가 폭주했는지.
#/proc/meminfo — 시스템 전체
$ cat /proc/meminfo | head -20MemTotal: 16777216 kBMemFree: 1024000 kBMemAvailable: 3072000 kB # ← 사용자 코드가 *알아야 할* 값Buffers: 128000 kBCached: 5000000 kB # page cacheSwapCached: 20000 kBActive: 7000000 kBInactive: 3000000 kBActive(anon): 5000000 kBInactive(anon): 2000000 kBActive(file): 2000000 kBInactive(file): 1000000 kBUnevictable: 0 kBMlocked: 0 kBSwapTotal: 8000000 kBSwapFree: 7900000 kBDirty: 50000 kBWriteback: 0 kBAnonPages: 7000000 kB # 모든 anon (heap+stack 등)Mapped: 1500000 kBShmem: 500000 kB- MemAvailable =
MemFree + reclaimable cache. 새 alloc이 어디까지 가능한가의 답. - MemFree만 보면 안 됨 — 캐시 회수 가능.
#캐시 vs 진짜 메모리
$ free -h total used free shared buff/cache availableMem: 16Gi 7Gi 1Gi 500Mi 8Gi 8.5GiSwap: 8Gi 100Mi 7.9Giused = total - free - buff/cache. buff/cache는 언제든 회수 가능 — available이 진짜 free.
캐시 강제 비우기 (디버깅용):
$ sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'#swappiness — swap 사용 성향
$ cat /proc/sys/vm/swappiness60 # 기본- 0: 거의 swap 안 함 (DB 서버 권장).
- 60: 기본.
- 100: 적극 swap.
swap이 나쁜 게 아니라 잘못된 page를 swap하면 느려짐. 운영에서는 swap 자체를 끄거나 vm.swappiness=10 정도.
#NUMA — 멀티 노드
$ numactl --hardwareavailable: 2 nodes (0-1)node 0 cpus: 0 1 2 3node 0 size: 8000 MBnode 0 free: 1500 MBnode 1 cpus: 4 5 6 7node 1 size: 8000 MBnode 1 free: 6000 MB
$ numastat -p 12345 Node 0 Node 1 Total --------------- --------------- ---------------Huge 0.00 0.00 0.00Heap 150.00 5500.00 5650.00Stack 0.00 0.00 0.00Private 12000.00 0.00 12000.00---------------- --------------- --------------- ---------------Total 12150.00 5500.00 17650.00NUMA 시스템에서 어느 노드에 메모리가 있는지. 잘못된 노드에 있으면 cross-node access로 느림.
$ numactl --cpunodebind=0 --membind=0 ./myprog # node 0 고정#valgrind / asan / heaptrack — 다음 장들
위는 어떤 영역이 메모리를 쓰는지 큰 그림. 누수의 정확한 라인은:
- AddressSanitizer의 leak detector (Sanitizers Ch 3)
- Valgrind Memcheck (Valgrind Ch 1)
- heaptrack (이 시리즈 Ch 2)
- glibc mtrace (이 시리즈 Ch 4)
- jemalloc/tcmalloc profiling (Ch 3)
#자주 만나는 함정
| 증상 | 원인 |
|---|---|
| RSS는 안 늘어났는데 VSS는 늘어남 | mmap만 하고 touch 안 함. 위험은 future swap thrashing |
| RSS도 정상인데 OOM | 다른 프로세스가 부풀어. top 정렬 |
| RSS 줄지 않음 (free 후) | glibc free가 반환 안 함. malloc_trim 또는 jemalloc 사용 |
free 출력 used가 큼 | 캐시 포함. available 봐야 |
| 한 프로세스의 PSS << RSS | 공유 라이브러리 많음. 누수 아님 |
| Shared 갑자기 증가 | mmap 공유 영역 (DB 페이지 캐시 등) |
| /proc/…/smaps 너무 큼 | smaps_rollup 사용 (5.0+) |
| THP huge page 매핑 | AnonHugePages로 별도 카운트 |
#정리
- VSS/RSS/PSS/USS 구분 필수. PSS가 공정한 회계.
/proc/[pid]/status가 빠른 RSS,smaps_rollup이 PSS.smaps로 영역별 분해. pmap도 같은 정보.- RssAnon vs RssFile vs RssShmem이 누수 종류 구분.
- OOM 점수는 RSS 기반. 보호하려면
oom_score_adj. MemAvailable이 실제 free,free는 캐시 회수 가능량 모름.- NUMA 시스템은 노드별 회계 필수.
#다음 장 예고
Ch 2 — heaptrack. Valgrind보다 가벼운 heap profiler. 운영 환경에도 적용 가능.
#관련 항목
Memory Diagnostics · 1 of 7
- 1리눅스 메모리 회계 — RSS·VSS·PSS·smaps 해석
- 2heaptrack 분석 — 가벼운 heap profiler 활용
- 3jemalloc·tcmalloc Profiling — 운영 allocator의 진단 기능
- 4glibc 메모리 도구 — mtrace·mcheck·MALLOC_CHECK_
- 5운영 메모리 누수 진단 — long-running 프로세스의 진단 전략
- 6CXL 메모리 진단 — RAS·Poison List·Media Error 추적
- 7Tiered Memory 진단 — DAMON·DAMOS·Promotion/Demotion 디버깅
관련 글
운영 메모리 누수 진단 — long-running 프로세스의 진단 전략
장기 실행 서비스의 누수 추적. /proc 모니터링, cgroup memory.max, OOM 회피.
glibc 메모리 도구 — mtrace·mcheck·MALLOC_CHECK_
별 라이브러리 없이 glibc 만으로 메모리 디버깅. mtrace, mcheck, MALLOC_CHECK_.
jemalloc·tcmalloc Profiling — 운영 allocator의 진단 기능
표준 glibc malloc 대체 + 내장 profiler. pprof로 시각화.