/proc/<PID>/smaps로 익명 메모리가 늘어나는 매핑을 찾았다면 다음 질문은 “어느 호출 경로가 할당을 만들었는가?”다. 이때 eBPF 기반 도구로 사용자 공간 allocator 함수에 uprobe를 붙여 호출 스택과 요청 크기를 관찰할 수 있다.
중요한 경계부터 말하면, malloc 호출 횟수만 세어서는 메모리 누수를 증명할 수 없다. 반환된 주소와 크기를 기록하고 대응하는 free를 제거한 뒤, 일정 시간 남아 있는 outstanding allocation을 봐야 누수 후보에 가까워진다.
smaps와 할당 추적이 답하는 질문
| 도구 | 답하기 좋은 질문 |
|---|---|
/proc/PID/smaps |
어떤 매핑과 메모리 성격이 커졌는가? |
| heap profiler | 언어·런타임이 아는 객체와 할당 지점은 어디인가? |
| eBPF uprobe | 실행 중인 프로세스에서 어떤 allocator 함수와 호출 스택이 관찰되는가? |
smaps에서 Anonymous가 늘었다고 곧바로 malloc만 추적하는 것도 성급하다. Java·Go처럼 자체 런타임과 GC를 쓰거나 jemalloc·tcmalloc을 쓰는 프로세스는 glibc malloc이 핵심 경로가 아닐 수 있다.
먼저 대상과 allocator를 확인한다
pid=1234
readlink -f "/proc/$pid/exe"
cat "/proc/$pid/maps" | grep -E 'libc|jemalloc|tcmalloc'
확인할 것은 다음과 같다.
- 정확한 PID와 실행 파일
- glibc, jemalloc, tcmalloc 또는 언어 런타임 중 무엇이 할당하는지
- 심볼이 제거됐는지와 사용자 스택을 해석할 디버그 심볼이 있는지
- 컨테이너·PID namespace에서 어느 호스트 PID에 붙어야 하는지
bpftrace로 요청한 할당량을 빠르게 보기
bpftrace는 라이브러리 이름을 /etc/ld.so.cache에서 해석할 수 있다. 다음 예시는 특정 PID의 malloc 진입점에서 호출 스택별 요청 바이트 합계를 집계한다.
pid=1234
sudo bpftrace -p "$pid" -e '
uprobe:libc:malloc
{
@[ustack] = sum(arg0);
}'
arg0는 malloc(size_t size)의 첫 번째 인자다. 이 수치는 호출자가 요청한 누적 크기이지 현재 살아 있는 메모리도, RSS 증가량도 아니다. allocator가 내부적으로 반올림한 크기, 재사용한 arena, 실제 page fault와도 다를 수 있다.
호출 수가 필요하면 sum(arg0) 대신 count()를 쓸 수 있다. 그러나 높은 빈도의 할당에서 모든 사용자 스택을 수집하면 부하가 커질 수 있으므로 짧은 시간과 한 PID로 범위를 제한한다.
outstanding allocation은 BCC memleak으로 보기
BCC의 memleak 도구는 사용자 공간 할당과 해제 함수를 함께 추적하고, 아직 해제되지 않은 주소를 스택별로 집계한다.
sudo memleak -p 1234 5 6
위 형태는 PID 1234를 추적해 5초 간격으로 6회 보고하는 예시다. 배포판에 따라 실행 파일이 /usr/share/bcc/tools/memleak에 있을 수 있다.
할당이 매우 잦다면 샘플링과 최소 크기를 사용해 관찰 비용을 줄일 수 있다.
sudo memleak -p 1234 --sample-rate 10 --min-size 4096 5 6
남아 있는 할당은 어디까지나 후보다. 정상적으로 오래 사는 캐시나 초기화 데이터도 출력될 수 있다. 같은 부하를 반복해 오래된 할당량이 계속 증가하는지, 서비스의 heap 지표와 함께 확인한다.
malloc과 free를 직접 짝지을 때 필요한 것
직접 추적기를 만든다면 다음 상태가 모두 필요하다.
malloc진입 시 스레드별 요청 크기를 임시 저장한다.uretprobe에서 반환 주소와 크기, 호출 스택을 기록한다.free(ptr)가 호출되면 해당 주소를 맵에서 제거한다.calloc,realloc,posix_memalign같은 다른 경로도 처리한다.- 프로세스 종료, 실패한 할당과 주소 재사용을 처리한다.
bpftrace 문서에서 uprobe는 인자를, uretprobe는 반환값을 읽는 사용자 공간 probe로 구분한다. 런타임에 따라 return probe가 안전하지 않거나 스택 해석이 제한될 수 있으므로 대상별 검증이 필요하다.
운영 환경에서의 안전 체크
- 먼저 스테이징이나 동일한 부하 재현 환경에서 오버헤드를 측정한다.
- 호스트 전체가 아니라 PID, 함수, 크기와 시간으로 범위를 제한한다.
- 스택·주소·문자열에 민감 정보가 포함될 수 있어 출력 접근과 보존 기간을 제한한다.
- 심볼이 보이지 않는 결과를 추측으로 함수 이름에 연결하지 않는다.
- 관찰을 끝내면 probe가 해제됐는지 확인한다.
- eBPF 실행 권한은 강력하므로 최소 인원과 감사 가능한 절차로 관리한다.
분석 흐름을 한 줄로 연결하면
RSS 증가 확인
→ smaps로 증가 매핑과 Anonymous/PSS 확인
→ 런타임·allocator 확인
→ profiler 또는 eBPF로 호출 경로 추적
→ outstanding allocation 추세와 코드 수명주기 대조
eBPF는 코드를 수정하지 않고 관찰 지점을 추가할 수 있다는 장점이 있다. 하지만 항상 낮은 오버헤드이거나 자동으로 누수를 찾아 주는 도구는 아니다. 무엇을 추적했고 무엇은 보지 못했는지 함께 기록해야 결과를 믿을 수 있다.
관련 글: /proc/PID/smaps로 RSS·PSS를 나누는 법, eBPF가 안전한 이유와 위험해지는 조건
참고 문서
'배움과 성장 > 시스템·성능' 카테고리의 다른 글
| eBPF 동작 원리: bytecode·verifier·map·helper·JIT 흐름 (0) | 2026.04.08 |
|---|---|
| eBPF verifier가 보장하는 것과 보장하지 않는 것 (0) | 2026.04.07 |
| /proc/PID/smaps 메모리 분석: RSS·PSS·Anonymous를 구분하는 법 (0) | 2026.04.05 |
| free 후 RSS가 줄지 않는 이유: glibc 재사용·mmap·trim 구분 (0) | 2026.04.04 |
| malloc 성능: 빠른 경로와 느린 경로가 지연을 바꾸는 이유 (0) | 2026.04.03 |
댓글