C
운영체제/메모리/Lesson 03

메모리 관리 — 가상 메모리·페이징·할당

45분·theory

메모리 관리 — 가상 메모리·페이징·할당

🎯 이 lesson 을 읽고 나면

이 lesson 을 다 읽고 나면 아래 3가지를 자신 있게 할 수 있습니다.

  • ✅ Stack / Heap / Code / Data 영역
  • ✅ Paging vs Segmentation + Virtual Memory
  • ✅ Page Fault + LRU 페이지 교체

학습 목표를 체크리스트로 두고 다 답할 수 있게 되면 lesson 을 닫으세요.

가상 메모리 — *환상의 메모리 공간*

한 줄: 각 프로세스가 모든 메모리를 자기 것처럼 보는 환상. 실제로는 OS 가 페이지 단위로 물리 RAM 에 매핑.

왜 필요한가:

이유의미
격리프로세스 A 가 B 메모리 접근 X (보안·안정성)
추상화0x00000000 부터 시작하는 통일된 주소 (개발 단순화)
오버커밋16GB RAM 에서 64GB 가상 메모리 사용 가능 (페이지 단위 로드)
스왑안 쓰는 페이지를 디스크로 (RAM 더 효율)

변수 접근 6단계 (예: int x = 5; printf("%d", x);):
1. 가상 주소&x = 0x7fff5fbff8ac (스택 영역)
2. MMU 변환 — Memory Management Unit 이 페이지 테이블 조회
3. TLB 캐시 — 자주 쓰는 매핑은 CPU 캐시에 (1-cycle hit)
4. 물리 주소 — 0x7fff5fbff8ac → 물리 RAM 0x12345678
5. 캐시 확인 — L1 → L2 → L3 → RAM (4 → 12 → 40 → 200 cycles)
6. 값 반환5 가 레지스터로

> 💡 Page Fault = 페이지 테이블에 매핑 없음 → OS 가 디스크에서 로드 → 매우 느림 (수 ms).

페이징 — 4KB 단위 메모리 관리

페이지 = 4KB (Linux 표준). 가상·물리 메모리 모두 페이지 단위로 관리.

페이지 테이블 구조:

  • 각 프로세스마다 별도 페이지 테이블
  • 가상 페이지 번호 → 물리 페이지 번호 + 권한(r/w/x)
  • 64bit 시스템 = 4단계 페이지 테이블 (PML4 → PDPT → PD → PT)

TLB (Translation Lookaside Buffer):

  • CPU 안 페이지 매핑 캐시 (보통 64-1024 항목)
  • 히트 시 1 cycle, 미스 시 페이지 테이블 워크 (10-100 cycles)

페이징 함정:

문제원인해결
Page Fault매핑 없음 → 디스크 로드자주 쓰는 데이터는 LRU 캐시
Thrashing페이지 폴트 너무 잦음 → CPU 가 디스크만 기다림RAM 늘리기·작업 세트 축소
메모리 단편화작은 빈 공간 흩어짐Buddy/Slab 할당자 (커널이 처리)
OOM KillerRAM + 스왑 다 차면 OS 가 프로세스 강제 종료oom_score 낮게·메모리 누수 수정

Huge Page (2MB·1GB):

  • 큰 메모리를 다루는 DB·JVM 에서 사용
  • TLB 미스 감소 → 성능 향상
  • Linux: echo 1024 > /proc/sys/vm/nr_hugepages

메모리 누수 + OOM 디버깅

메모리 누수 (Memory Leak): 할당했지만 반환 안 함 → 시간 지날수록 누적 → 결국 OOM.

언어별 위험도:

언어누수 위험이유
C/C++매우 높음mallocfree 안 부르면 즉시 누수
Java/Python/Go낮음 (GC)단, 참조 보유 시 GC 가 회수 못 함 (Listener·캐시·전역 변수)
Rust거의 X소유권 시스템이 컴파일 타임에 보장

누수 진단 도구:

  • Linux: valgrind --leak-check=full ./app (C/C++)
  • Java: jmap -heap <PID>jhat 또는 VisualVM
  • Python: tracemalloc 모듈 또는 objgraph
  • Node.js: --inspect + Chrome DevTools Heap Snapshot
  • 모든 언어: top 또는 htop 에서 RSS 가 시간 지남에 따라 계속 증가 하면 의심

OOM Killer 동작:
1. RAM + 스왑 거의 다 참
2. 커널이 oom_score 계산 (메모리 사용량·우선순위 기반)
3. 가장 점수 높은 프로세스 SIGKILL
4. /var/log/syslog 또는 dmesg 에 기록 (Out of memory: Kill process ...)

> 💡 production server 에서는 vm.swappiness=10 (스왑 최소화) + 메모리 모니터링 알람 필수.

💻 📌 시나리오로 배우는 메모리 분석
# ============================================================
# 시나리오 1: "서버 메모리가 부족하다는 알람이 왔다"
# ============================================================
# 시스템 전체 상태부터 — 사람이 읽기 쉽게
free -h
#               total   used   free  shared  buff/cache   available
# Mem:          15Gi    11Gi   1.2Gi  200Mi   3.0Gi       3.5Gi
# Swap:         2.0Gi   1.5Gi  500Mi
#
# 핵심:
#   available  = *실제로 새로 쓸 수 있는* 메모리 (used 가 아닌 이거 봐야 함)
#   buff/cache = 디스크 캐시 (필요 시 자동 회수)
#   swap used > 0 = RAM 부족 신호 ⚠️

# 더 자세히 (50+ 항목)
cat /proc/meminfo

# 1초 간격 추세 보기
vmstat 1 5                    # 5번 출력
# free·si(swap in)·so(swap out) 컬럼 주목

# ============================================================
# 시나리오 2: "어느 프로세스가 메모리 많이 쓰지?"
# ============================================================
ps aux --sort=-%mem | head -10
# RSS 컬럼 = 실제 사용 메모리 (KB)
# VSZ 컬럼 = 가상 메모리 (참고용)

# 컬러 인터랙티브
top -o %MEM                   # M 키로 정렬도 가능

# 특정 프로세스의 *메모리 맵* 상세
pmap -x 28391                 # 라이브러리·힙·스택별 사용량
cat /proc/28391/status | grep -E 'VmRSS|VmSize|VmSwap'
#   VmRSS = 실제 물리 메모리 (RAM)
#   VmSize = 가상 메모리 (예약된 주소 공간)
#   VmSwap = 스왑된 양

# ============================================================
# 시나리오 3: "OOM Killer 가 내 프로세스를 죽였다"
# ============================================================
# 1) OOM 이벤트 확인
dmesg | grep -i 'out of memory'
sudo journalctl -k | grep -i oom
# 출력 예: Out of memory: Kill process 28391 (java) score 854 or sacrifice child

# 2) 프로세스의 OOM 점수 확인 (높을수록 먼저 죽음)
cat /proc/28391/oom_score
# 0~1000. 메모리 사용량·우선순위 기반

# 3) 중요한 프로세스 보호
echo -1000 > /proc/28391/oom_score_adj  # 절대 안 죽임 (root 만)
# 또는 systemd 유닛 파일에:
#   OOMScoreAdjust=-500

# 4) 스왑 조정 (서버에선 보통 낮춤)
cat /proc/sys/vm/swappiness          # 기본 60
sudo sysctl vm.swappiness=10         # production 권장 (스왑 최소화)

# ============================================================
# 시나리오 4: "Java 앱이 메모리 누수 의심된다"
# ============================================================
# 1) 힙 통계 한 줄
jmap -heap 28391
# Heap Configuration:
#   MaxHeapSize = 4096 MB
# Heap Usage:
#   PS Young Generation: Eden 256/256MB (100% used)  ← 위험
#   PS Old Generation:   2800/3072 MB (91% used)     ← 매우 위험

# 2) GC 통계 1초마다 (장기 추세 보기)
jstat -gcutil 28391 1s
# S0  S1  E    O    M    YGC  YGCT  FGC  FGCT  GCT
# 0   85  92   91   95   1820 25.5  234  120   145
# FGC (Full GC) 자주 발생·시간 길면 = 누수 의심

# 3) 힙 덤프 (분석용)
jmap -dump:live,format=b,file=/tmp/heap.bin 28391

# 4) Eclipse MAT 또는 VisualVM 으로 열어 분석
# - Leak Suspects 리포트
# - 어떤 클래스가 메모리 차지하는지

# ============================================================
# 시나리오 5: "Python 앱이 시간 지날수록 RSS 증가"
# ============================================================
# tracemalloc — 메모리 할당 추적 (코드 안에 삽입)
# import tracemalloc
# tracemalloc.start()
# ... 코드 실행 ...
# snapshot = tracemalloc.take_snapshot()
# for stat in snapshot.statistics('lineno')[:10]:
#     print(stat)
# 출력: /app/main.py:42: size=125 MB ...

# memory-profiler — 줄 단위 메모리 측정
# pip install memory-profiler
# python -m memory_profiler app.py

# ============================================================
# 시나리오 6: "Huge Page 활성화로 성능 개선"
# ============================================================
# 큰 메모리 다루는 DB·JVM 에서 TLB 미스 감소
cat /proc/sys/vm/nr_hugepages         # 현재 (보통 0)
echo 1024 | sudo tee /proc/sys/vm/nr_hugepages   # 1024 × 2MB = 2GB
# 영구 적용: /etc/sysctl.conf 에 vm.nr_hugepages=1024

🤖 AI 에게 이렇게 요청해보세요

이 lesson 의 개념을 알면 AI 에게 구체적으로 지시할 수 있습니다. 막연한 "고쳐줘" 가 아니라 어휘를 가진 요청 — 그게 토큰 절약의 출발점입니다.

  • "이 코드의 메모리 사용 패턴 (heap/stack) 분석해줘"
  • "이 프로세스의 RSS / Virtual Memory 측정 명령어 알려줘"

왜 이게 토큰을 줄이나

개념을 모를 땐 AI 답변을 받고도 "그게 뭐예요?" 를 다시 물어야 합니다. 그 "다시 물음" 이 토큰을 잡아먹습니다. 개념 한 번 익혀두면 대화가 한 번에 끝납니다.

메모리 관리 — 가상 메모리·페이징·할당 - 운영체제