/proc/PID/smaps 메모리 분석: RSS·PSS·Anonymous를 구분하는 법

반응형

프로세스 RSS가 늘었다고 바로 메모리 누수라고 결론 내릴 수는 없다. RSS에는 익명 메모리, 실행 파일과 공유 라이브러리의 파일 매핑, 공유 메모리 등 현재 RAM에 올라온 여러 페이지가 함께 잡힌다.

Linux의 /proc/<PID>/smaps는 이 합계를 가상 메모리 매핑별로 나눠 보여 준다. 원인을 확정하는 도구라기보다, 다음 조사 대상을 좁히는 도구에 가깝다.

smaps와 smaps_rollup의 차이

pid=1234
sudo cat "/proc/$pid/smaps_rollup"
sudo less "/proc/$pid/smaps"
  • smaps_rollup: 프로세스 전체의 주요 항목을 합산해 빠르게 본다. 커널에서 제공하는 경우에 사용할 수 있다.
  • smaps: 주소 범위와 권한, 파일 경로를 포함해 매핑 하나씩 본다.

다른 사용자의 프로세스는 권한이나 ptrace 정책 때문에 읽지 못할 수 있다. 컨테이너 안에서는 PID namespace가 달라 호스트 PID와 컨테이너 PID도 구분해야 한다.

먼저 읽을 핵심 필드

필드 의미 해석할 때의 주의점
Size 해당 가상 주소 매핑의 크기 실제 RAM 사용량이 아니다.
Rss 현재 RAM에 상주한 페이지 크기 공유 페이지가 프로세스마다 중복 집계될 수 있다.
Pss 공유 페이지를 공유 프로세스 수에 비례해 나눈 값 여러 프로세스의 합산 비용을 비교할 때 유용하다.
Private_Clean 이 프로세스에만 귀속되고 수정되지 않은 상주 페이지 파일 재로딩 가능 여부를 함께 봐야 한다.
Private_Dirty 이 프로세스에만 귀속되고 수정된 상주 페이지 크다고 곧바로 누수라는 뜻은 아니다.
Shared_Clean/Dirty 다른 프로세스와 공유되는 깨끗한·수정된 페이지 라이브러리·공유 매핑의 성격을 함께 확인한다.
Anonymous 파일에 속하지 않는 상주 메모리 힙만이 아니라 익명 mmap과 copy-on-write 페이지도 포함한다.
Swap 해당 매핑에서 swap으로 나간 메모리 지연과 메모리 압력의 맥락을 같이 본다.

PSS가 RSS보다 무조건 ‘더 정확한 단 하나의 값’인 것은 아니다. 질문이 다르다. 한 프로세스가 접근하는 상주 페이지 총량을 보려면 RSS가, 공유 비용을 나눠 여러 프로세스의 메모리 기여도를 합산하려면 PSS가 더 적합하다.

매핑 헤더부터 읽기

각 블록의 첫 줄은 대략 다음 모양이다.

7f2c1a000000-7f2c1a200000 rw-p 00000000 00:00 0 [heap]

확인할 내용은 네 가지다.

  1. 주소 범위
  2. rwx 접근 권한과 private/shared 표시
  3. 파일 경로 또는 [heap], [stack] 같은 이름
  4. 그 아래의 RSS·PSS·Anonymous·Dirty 값

이름이 [heap]이라고 표시된 영역만 동적 할당 메모리인 것도 아니다. 현대 할당자는 큰 요청이나 arena를 익명 mmap으로 확보할 수 있다. 반대로 mmap은 파일 기반일 수도, 익명일 수도 있다. 따라서 heap·file·mmap을 서로 배타적인 세 종류로 나누면 안 된다.

합계에서 큰 매핑으로 내려가기

전체 합계를 먼저 확인한다.

pid=1234
sudo grep -E '^(Rss|Pss|Private_|Shared_|Anonymous|Swap):' "/proc/$pid/smaps_rollup"

그다음 smaps에서 매핑별 PSS와 Anonymous가 큰 구간을 찾는다. 단일 시점보다 같은 부하 구간의 스냅샷을 반복해서 비교하는 것이 중요하다.

pid=1234
sudo awk '
  /^[0-9a-f]+-[0-9a-f]+/ { mapping=$0 }
  /^Pss:/ && $2 > 10240 { print $2 " kB\t" mapping }
' "/proc/$pid/smaps"

이 예시는 PSS가 10MiB보다 큰 매핑을 표시한다. 임계값은 워크로드에 맞춰 바꾼다.

증가 패턴을 어떻게 해석할까

Anonymous와 Private_Dirty가 함께 증가

애플리케이션이 실제로 쓰는 익명 페이지가 늘고 있을 가능성이 있다. 정상 캐시, 런타임 heap 성장, allocator fragmentation, 해제되지 않은 객체 모두 후보다. 누수 확정이 아니라 할당 경로 조사 신호로 본다.

파일 경로가 있는 Shared_Clean 증가

공유 라이브러리나 읽기 전용 파일 매핑이 상주하기 시작했을 수 있다. 파일 기반 페이지는 필요할 때 회수될 수 있으므로 메모리 압력과 재사용 패턴을 함께 확인한다.

가상 Size만 크게 증가

주소 공간을 예약했지만 아직 페이지가 실제로 fault-in되지 않았을 수 있다. RSS·PSS가 함께 늘었는지 확인한다.

Swap이 증가

프로세스의 익명 페이지 일부가 swap으로 이동했다. 단순 용량뿐 아니라 major fault, PSI memory pressure와 지연 변화를 같이 본다.

누수 판단까지 가는 순서

  1. 같은 워크로드에서 RSS·PSS의 시간 추세를 기록한다.
  2. smaps_rollup으로 Anonymous·Private·Shared·Swap 변화를 나눈다.
  3. smaps에서 증가를 주도하는 매핑을 찾는다.
  4. 애플리케이션 캐시, 런타임 GC와 allocator 동작을 확인한다.
  5. 재현 가능한 경우 heap profiler나 할당 추적으로 호출 경로를 찾는다.

Private_Dirty가 크다는 사실만으로 “진짜 내가 쓰는 메모리”나 “누수”라고 부르면 한 단계가 빠져 있다. smaps는 무엇이 상주하고 있는지 보여 주고, 왜 남았는지는 다음 도구로 검증해야 한다.

할당 호출 경로를 좁히는 단계는 eBPF로 사용자 공간 메모리 할당 추적하기에서 이어서 다룬다.

참고 문서

반응형
KEEP READING
카테고리 전체 보기 →

댓글