Home Linux AWS Cloud Docker Python AI
클릭하여 터미널 활성화

Linux OOM Killer 프로세스가 갑자기 죽는 이유와 방어법

// Linux · OOM Killer · 메모리 · 트러블슈팅 · Rocky Linux · 서버 관리
리눅스 서버에서 프로세스가 갑자기 죽는다. 로그도 없이 사라진다. 범인은 대부분 OOM Killer다. 커널이 메모리 부족 상황에서 시스템을 살리기 위해 프로세스를 강제로 죽이는 메커니즘이다. OOM Killer의 동작 원리를 이해하고, Rocky Linux 9에서 직접 재현하며, 진단과 방어법을 정리한다.
📋 이 글에서 다루는 것
OOM Killer의 동작 원리와 프로세스가 선택되는 기준 (oom_score)
dmesg, journalctl, Docker에서 OOM 발생 여부를 확인하는 방법
메모리 누수 범인 찾기 — RSS, /proc/meminfo, top 활용
OOM 방어법 — oom_score_adj, systemd 영구 설정, Swap 추가, overcommit 정책
Docker 컨테이너에서 OOM을 직접 재현하는 방법
OOM Killer란?

리눅스 커널은 메모리가 완전히 고갈되면 시스템 전체가 멈추는 것을 막기 위해 OOM(Out Of Memory) Killer를 실행한다. OOM Killer는 메모리를 가장 많이 쓰고 있는 프로세스를 골라서 SIGKILL(9) 신호로 강제 종료한다.

프로세스 입장에서는 아무 예고 없이 죽기 때문에, 로그도 남기지 못하고 사라진다. "분명 실행해놨는데 없어졌다"는 상황이 바로 이것이다.

💡 핵심 — OOM Killer는 버그가 아니라 커널의 자기 방어 메커니즘이다. 문제는 OOM Killer가 아니라 메모리를 다 써버린 원인에 있다.

에러 메시지

OOM Killer가 작동하면 dmesg/var/log/messages에 아래와 같은 로그가 남는다.

dmesg 로그
[ 3412.876543] python3 invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0
[ 3412.876545] Out of memory: Killed process 1234 (python3) total-vm:524288kB, anon-rss:131072kB
[ 3412.876547] oom_reaper: reaped process 1234 (python3), now anon-rss:0kB
로그 항목의미
Killed process 1234 (python3)죽은 프로세스 PID와 이름
total-vm가상 메모리 사용량
anon-rss실제 물리 메모리 사용량 — 이게 핵심
⚠️ 주의 — 프로세스가 exit code 137로 종료됐다면 OOM Killer를 의심하라. 137 = 128 + 9(SIGKILL)이다.

시나리오 1. OOM 발생 확인하기

프로세스가 갑자기 죽었을 때, OOM Killer가 범인인지 확인하는 방법이다.

진단 — dmesg 확인
OOM 로그 검색
# dmesg에서 OOM 관련 로그 검색
$ dmesg | grep -i "oom\|killed process\|out of memory"
[ 3412.876543] python3 invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE)
[ 3412.876545] Out of memory: Killed process 1234 (python3) total-vm:524288kB, anon-rss:131072kB

# 시간 포함해서 보기
$ dmesg -T | grep -i "killed process"
[Sat Mar 22 14:23:45 2026] Out of memory: Killed process 1234 (python3)
진단 — /var/log/messages & journalctl
시스템 로그 확인
# /var/log/messages에서 확인
$ grep -i "oom\|killed process" /var/log/messages
Mar 22 14:23:45 server1 kernel: Out of memory: Killed process 1234 (python3)

# journalctl로 확인 (systemd 환경)
$ journalctl -k | grep -i "oom\|killed process"
Mar 22 14:23:45 server1 kernel: Out of memory: Killed process 1234 (python3)
진단 — 컨테이너 환경

Docker 컨테이너라면 호스트 dmesg가 아니라 Docker 자체에서 OOM 여부를 확인할 수 있다.

Docker 컨테이너 OOM 확인
# OOM으로 죽었는지 확인
$ docker inspect oom-lab --format '{{.State.OOMKilled}}'
true

# exit code 137 = OOM (128 + SIGKILL 9)
$ docker inspect oom-lab --format '{{.State.ExitCode}}'
137
💡 팁dmesg는 커널 링 버퍼라서 재부팅하면 사라진다. 영구 기록은 /var/log/messagesjournalctl에서 확인해야 한다.

시나리오 2. 어떤 프로세스가 죽을지 예측하기 — oom_score

OOM Killer는 아무 프로세스나 죽이지 않는다. 각 프로세스에 oom_score라는 점수를 매기고, 점수가 가장 높은 프로세스를 죽인다. 점수 범위는 0에서 1000까지이며, 1000에 가까울수록 먼저 죽는다.

oom_score 확인
oom_score 확인
# 특정 프로세스의 oom_score
$ cat /proc/1234/oom_score
666

# RSS(실제 물리 메모리) 기준 상위 프로세스
$ ps -eo pid,comm,rss --sort=-rss | head -10
  PID COMMAND           RSS
 1234 python3        131072
  456 mysqld          65536
  789 nginx           32768
전체 프로세스 oom_score 순위
# oom_score가 높은 순으로 정렬
$ for pid in $(ps -eo pid --no-headers); do
    if [ -f /proc/$pid/oom_score ]; then
      score=$(cat /proc/$pid/oom_score 2>/dev/null)
      cmd=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ' | head -c 30)
      [ -n "$cmd" ] && printf "PID %-6s score: %-5s %s\n" "$pid" "$score" "$cmd"
    fi
  done | sort -t: -k2 -rn | head -10
PID 1234   score: 666   python3 memory_leak.py
PID 456    score: 333   /usr/sbin/mysqld
PID 789    score: 167   nginx: worker process
oom_score 계산 기준설명
물리 메모리(RSS) 사용량높을수록 점수가 높음
점수 범위0 ~ 1000 (1000이면 가장 먼저 죽음)
root 프로세스약간의 감점 보너스를 받음
💡 팁oom_score는 읽기 전용이다. 커널이 메모리 사용량 기반으로 자동 계산한다. 우리가 조절할 수 있는 건 oom_score_adj다 (시나리오 4에서 설명).

시나리오 3. 메모리 누수 범인 찾기

OOM이 발생했다면, 어떤 프로세스가 메모리를 다 잡아먹었는지 찾아야 한다. 핵심은 VSZ(가상)가 아니라 RSS(실제 물리 메모리)를 봐야 한다는 것이다.

RSS 기준 상위 프로세스
메모리 사용량 상위 프로세스
$ ps aux --sort=-rss | head -10
USER       PID %CPU %MEM    VSZ   RSS TTY  STAT COMMAND
root      1234  5.2  62.3 524288 131072 ?   S    python3 memory_leak.py
mysql      456  1.2  31.1 262144  65536 ?   S    /usr/sbin/mysqld
root       789  0.5  15.5 131072  32768 ?   S    nginx: worker process
특정 프로세스 상세 메모리
$ cat /proc/1234/status | grep -i "vm\|rss"
VmPeak:   524288 kB    ← 최대 가상 메모리
VmSize:   524288 kB    ← 현재 가상 메모리
VmRSS:    131072 kB    ← 실제 물리 메모리 (핵심!)
VmSwap:        0 kB    ← 스왑 사용량
시스템 전체 메모리 상태
시스템 메모리 확인
$ free -h
              total    used    free  shared  buff/cache  available
Mem:          7.8Gi   7.1Gi   102Mi    12Mi      580Mi      412Mi
Swap:         1.0Gi   980Mi    44Mi

$ grep -E "MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree|Committed_AS" /proc/meminfo
MemTotal:        8025636 kB
MemFree:          104448 kB
MemAvailable:     422912 kB    ← 이게 0에 가까우면 위험
SwapTotal:       1048576 kB
SwapFree:          45056 kB    ← 스왑도 거의 다 참
Committed_AS:    9437184 kB    ← overcommit 포함
❌ 위험 신호 3가지MemAvailable이 전체의 10% 이하면 위험. SwapFree가 0에 가까우면 OOM 직전. Committed_AS가 MemTotal + SwapTotal보다 크면 overcommit 상태다.
top으로 실시간 모니터링
top 메모리 정렬 (Shift+M)
$ top -o %MEM -b -n 1 | head -15
top - 14:23:45 up 3 days,  5:12,  1 user
MiB Mem :   7838.5 total,    102.0 free,   7156.5 used,    580.0 buff/cache
MiB Swap:   1024.0 total,     44.0 free,   980.0 used.    412.0 avail Mem

  PID USER   PR  NI    VIRT    RES    SHR S %CPU  %MEM   COMMAND
 1234 root   20   0  512000 131072   1024 S  5.2  62.3   python3
  456 mysql  20   0  256000  65536   4096 S  1.2  31.1   mysqld
  789 root   20   0  128000  32768   2048 S  0.5  15.5   nginx
⚠️ 주의VSZ/VIRT(가상 메모리)가 아닌 RSS/RES(실제 물리 메모리)를 봐야 한다. VSZ는 예약만 한 메모리를 포함하기 때문에 실제 사용량과 다르다.

시나리오 4. OOM Killer 방어법

중요한 프로세스(DB, 웹서버 등)가 OOM Killer에 죽지 않도록 보호하는 방법이다.

방법 1 — oom_score_adj로 프로세스 보호
oom_score_adj 설정
# 현재 값 확인
$ cat /proc/$(pgrep mysqld)/oom_score_adj
0

# -1000으로 설정 → OOM Killer 대상에서 제외
$ echo -1000 > /proc/$(pgrep mysqld)/oom_score_adj

# 확인 — oom_score가 0으로 떨어짐
$ cat /proc/$(pgrep mysqld)/oom_score
0
oom_score_adj 값의미
-1000OOM Killer 대상에서 완전 제외 (절대 보호)
0기본값 — 커널이 알아서 점수 매김
+1000최우선 제거 대상 — 이 프로세스부터 죽임
방법 2 — systemd 서비스에 영구 설정

위 방법은 재부팅하면 초기화된다. systemd 서비스에 영구적으로 설정하려면 오버라이드 파일을 만들어야 한다.

systemd OOM 보호 설정
# mysqld 서비스 오버라이드 파일 생성
$ systemctl edit mysqld
# 에디터에서 아래 내용 추가:
[Service]
OOMScoreAdjust=-1000

# 적용 확인
$ systemctl cat mysqld | grep OOMScore
OOMScoreAdjust=-1000

# 서비스 재시작
$ systemctl restart mysqld

# 확인
$ cat /proc/$(pgrep mysqld)/oom_score_adj
-1000
방법 3 — Swap 추가

OOM이 자주 발생한다면 swap 공간을 추가해서 시간을 벌 수 있다. 다만 swap은 응급 조치일 뿐이다.

Swap 파일 추가 (2GB)
$ dd if=/dev/zero of=/swapfile bs=1M count=2048
2048+0 records in
2048+0 records out

$ chmod 600 /swapfile
$ mkswap /swapfile
Setting up swapspace version 1, size = 2 GiB

$ swapon /swapfile

# 확인
$ free -h
              total    used    free
Swap:         3.0Gi   980Mi   2.0Gi

# 재부팅 후에도 유지
$ echo '/swapfile swap swap defaults 0 0' >> /etc/fstab
⚠️ 중요 — Swap은 응급 조치다. Swap을 많이 쓰면 시스템이 극도로 느려진다(swap thrashing). 근본적인 해결은 메모리 누수를 잡거나 서버 메모리를 늘리는 것이다.
방법 4 — vm.overcommit_memory 설정

리눅스는 기본적으로 실제 메모리보다 더 많은 메모리를 할당할 수 있게 허용한다(overcommit). 이 정책을 변경하면 OOM 자체를 예방할 수 있다.

overcommit 정책 변경
# 현재 설정 확인
$ cat /proc/sys/vm/overcommit_memory
0

# 0 = 커널이 휴리스틱으로 판단 (기본값)
# 1 = 무조건 허용 (위험!)
# 2 = 물리 메모리 + swap 이상 할당 금지 (안전)

# 2로 변경 → OOM 대신 malloc 실패(ENOMEM) 발생
$ sysctl -w vm.overcommit_memory=2

# 영구 설정
$ echo 'vm.overcommit_memory=2' >> /etc/sysctl.d/99-oom.conf
$ sysctl -p /etc/sysctl.d/99-oom.conf
💡 주의사항overcommit_memory=2로 설정하면 OOM Killer가 작동할 일이 거의 없어진다. 대신 메모리 할당 요청이 거부(ENOMEM)되므로, 애플리케이션이 이를 처리할 수 있어야 한다. Redis 등 일부 애플리케이션은 overcommit_memory=1을 권장하니 확인 후 설정하라.

Docker로 OOM 직접 재현하기

실제로 OOM Killer를 발생시켜 보자. 메모리를 128MB로 제한한 컨테이너에서 200MB를 할당하면 OOM이 발생한다.

OOM 재현 — 128MB 제한 컨테이너
# 메모리 128MB 제한 컨테이너 실행
$ docker run -d --name oom-lab --memory=128m --memory-swap=128m rockylinux:9 sleep infinity

# 메모리 제한 확인
$ docker stats oom-lab --no-stream --format "{{.MemUsage}}"
688KiB / 128MiB

# 200MB 할당 시도 → OOM 발생!
$ docker exec oom-lab python3 -c "a = ' ' * (200 * 1024 * 1024)"
(프로세스가 SIGKILL로 종료, 출력 없음)

# exit code 확인
$ echo $?
137

# OOM 확인
$ docker inspect oom-lab --format '{{.State.OOMKilled}}'
true
✓ 재현 성공 — 128MB 제한 컨테이너에서 200MB를 할당하니 OOM Killer가 작동해서 exit code 137로 프로세스가 종료됐다.

트러블슈팅 플로우차트
프로세스가 갑자기 죽음 │ ├─ 1. OOM Killer가 범인인가? │ $ dmesg | grep -i "killed process"$ journalctl -k | grep -i oom │ → "Killed process" 로그 있음 → OOM 확정 │ → 로그 없음 → 다른 원인 (SIGSEGV, 수동 kill 등) │ ├─ 2. 어떤 프로세스가 메모리를 먹었나? │ $ ps aux --sort=-rss | head -10$ free -h │ → RSS가 비정상적으로 큰 프로세스 = 범인 │ ├─ 3. 응급 조치 │ → 문제 프로세스 재시작 (메모리 해제) │ → swap 추가 (시간 벌기) │ └─ 4. 재발 방지 → oom_score_adj 설정 (중요 프로세스 보호) → systemd OOMScoreAdjust 영구 설정 → 메모리 누수 수정 또는 서버 메모리 증설 → overcommit_memory 정책 검토

자주 쓰는 명령어 정리
OOM troubleshooting cheatsheet
# OOM 로그 확인
$ dmesg -T | grep -i "oom\|killed process"
$ journalctl -k | grep -i "oom\|killed process"
$ grep -i "oom\|killed process" /var/log/messages

# 메모리 상태 확인
$ free -h
$ cat /proc/meminfo

# 메모리 많이 쓰는 프로세스 (RSS 기준)
$ ps aux --sort=-rss | head -10

# oom_score 확인
$ cat /proc/<PID>/oom_score
$ cat /proc/<PID>/oom_score_adj

# 프로세스 OOM 보호 (-1000 = 제외)
$ echo -1000 > /proc/<PID>/oom_score_adj

# systemd 서비스 OOM 보호 (영구)
$ systemctl edit <서비스명>
# → [Service] OOMScoreAdjust=-1000

# swap 확인 & 추가
$ swapon --show
$ dd if=/dev/zero of=/swapfile bs=1M count=2048
$ chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile

# overcommit 정책 확인
$ cat /proc/sys/vm/overcommit_memory

# Docker 컨테이너 OOM 확인
$ docker inspect <컨테이너> --format '{{.State.OOMKilled}}'

테스트 환경
항목버전
OSRocky Linux 9.3 (Blue Onyx)
Kernel5.14
Docker메모리 제한(--memory=128m)으로 OOM 재현

자주 묻는 질문 (FAQ)
Q. OOM Killer가 작동했는데 dmesg에 로그가 없다면?
dmesg는 커널 링 버퍼로, 크기가 제한되어 있고 재부팅하면 사라진다. 로그가 덮여 쓸 수 있으므로, /var/log/messagesjournalctl -k에서 영구 기록을 확인하라. 이것도 없다면 journalctl --vacuum-size로 로그가 잘려나간 게 아닌지 확인한다.
Q. oom_score_adj를 -1000으로 설정하면 절대 안 죽나?
거의 그렇다. -1000으로 설정된 프로세스는 OOM Killer 대상에서 제외된다. 하지만 모든 프로세스를 -1000으로 설정하면 안 된다 — 커널이 죽일 프로세스를 찾지 못하면 시스템 전체가 멈추는 커널 패닉이 발생할 수 있다.
Q. exit code 137이면 무조건 OOM인가?
아니다. 137 = 128 + 9(SIGKILL)이므로, 누군가 kill -9로 수동 종료한 경우에도 137이 나온다. OOM인지 확인하려면 반드시 dmesgjournalctl에서 "Killed process" 로그를 확인해야 한다. Docker 환경이라면 docker inspect --format '{{.State.OOMKilled}}'로 직접 확인 가능하다.
Q. overcommit_memory를 2로 설정하면 OOM이 절대 안 일어나나?
overcommit_memory=2는 물리 메모리 + swap 이상의 메모리를 할당하지 않겠다는 의미다. OOM 발생 확률이 크게 줄어들지만, 대신 메모리 할당 요청이 거부(ENOMEM)될 수 있다. 애플리케이션이 ENOMEM을 제대로 처리하지 못하면 다른 방식으로 크래시할 수 있다.
Q. Swap을 늘리면 OOM이 안 발생하나?
Swap은 OOM이 발생하기까지의 시간을 늘려줄 뿐이다. 메모리 누수가 있으면 결국 swap까지 다 차고 OOM이 발생한다. 그리고 swap을 많이 쓰면 디스크 I/O가 급증하면서 시스템이 극도로 느려진다(swap thrashing). Swap은 응급 조치이고, 근본적인 해결은 메모리 누수를 잡거나 서버 메모리를 늘리는 것이다.

📌 이 글 핵심 요약
OOM Killer는 커널의 자기 방어 메커니즘이다 — 메모리 고갈 시 oom_score가 가장 높은 프로세스를 SIGKILL로 강제 종료한다
dmesg -T | grep "killed process"로 OOM 발생 여부를 확인한다 — exit code 137은 SIGKILL을 의미한다
ps aux --sort=-rss로 메모리 범인을 찾고, free -h/proc/meminfo로 시스템 상태를 파악한다
중요 프로세스는 oom_score_adj=-1000으로 보호하고, systemd의 OOMScoreAdjust로 영구 설정한다
Swap 추가는 응급 조치일 뿐이다 — 근본적인 해결은 메모리 누수 수정 또는 서버 메모리 증설이다