본문 바로가기
Q&A해결2026년 7월 21일1분 읽기

쿠버네티스에서 자바 파드가 메모리 limit 한참 밑인데도 OOMKilled 되는 이유가 뭘까요

k
k8s_jiwon
조회 807댓글 4

스프링부트 앱을 쿠버네티스에 올렸는데 트래픽이 좀 몰리면 파드가 OOMKilled(137)로 죽습니다. limits.memory는 1Gi로 줬고, 앱에 -Xmx512m 줘서 힙은 절반밖에 안 쓰게 했는데도요. kubectl top으로 보면 죽기 직전에 1Gi를 꽉 채우더라고요.

describe 하면 이렇게 나옵니다.

    State:          Running
      Last State:   Terminated
      Reason:       OOMKilled
      Exit Code:    137
    Limits:
      memory:  1Gi

힙을 512로 묶었는데 왜 컨테이너 전체 메모리가 1Gi를 넘기는 건지 이해가 안 됩니다. 힙 말고 어디서 새는 걸까요?

댓글 4

김김영삼3개월 전
자바에서 -Xmx는 힙 상한일 뿐이고 JVM이 실제로 먹는 RSS는 힙 + 비힙이 다 합쳐진 값이에요. 메타스페이스, 스레드 스택(스레드당 보통 1MB), JIT 코드캐시, GC 구조체, 그리고 네티나 NIO 쓰면 direct byte buffer(오프힙)까지 더해집니다. 그래서 힙 512라도 스레드가 수백 개 뜨거나 다이렉트 버퍼가 커지면 컨테이너 RSS는 금방 1Gi를 넘겨요. 넘기는 순간 커널 cgroup이 137로 쳐냅니다.

권장하는 방식은 -Xmx를 고정하지 말고 -XX:MaxRAMPercentage로 컨테이너 메모리 대비 비율로 잡는 거예요. 자바 11 이상은 cgroup limit을 인식해서 1Gi를 컨테이너 메모리로 봅니다.

-XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=70

이렇게 하면 힙을 700MB쯤 쓰고 나머지를 비힙 여유로 남겨요. 지금처럼 512 고정에 limit 1Gi면 비힙이 500MB나 있어서 오히려 힙을 덜 쓰고 있는 상태일 수도 있고요. 그리고 kubectl top은 RSS 기준이라 힙 사용량이랑 다르니 참고만 하세요.
kk8s_jiwon(guest)3개월 전
아 오프힙이 문제일 수 있겠네요. 저희 앱이 웹플럭스라 네티를 쓰는데, 다이렉트 버퍼가 얼마나 쓰이는지 컨테이너 안에서 확인할 방법이 있을까요? 그냥 limit을 2Gi로 올리는 게 나을지도 고민이고요.
김김영삼3개월 전
네티면 다이렉트 버퍼 의심이 맞아요. 확인은 Native Memory Tracking을 켜면 됩니다. 실행 옵션에 -XX:NativeMemoryTracking=summary 주고, 파드 안에서 jcmd 프로세스ID VM.native_memory summary 찍으면 힙/메타스페이스/스레드/인터널 각각 얼마 쓰는지 카테고리별로 나와요. Internal이나 Other가 크면 다이렉트 버퍼예요.

네티 다이렉트 메모리는 -Xmx랑 별개로 -XX:MaxDirectMemorySize나 io.netty.maxDirectMemory 시스템 프로퍼티로 상한을 걸 수 있어요. 안 걸면 기본이 -Xmx랑 같은 값이라 최악의 경우 힙만큼 오프힙을 또 쓸 수 있습니다. 그래서 limit만 2Gi로 올리면 당장 안 죽더라도 버퍼가 계속 늘면 또 터져요. NMT로 범인부터 특정하고 상한을 거는 순서를 추천합니다. 트래픽 대비 진짜 부족한 거면 그때 limit도 같이 올리고요.
kk8s_jiwon(guest)3개월 전
NMT 찍어보니 Internal이 400MB 넘게 잡혀서 다이렉트 버퍼가 맞았네요. MaxDirectMemorySize 걸고 RAMPercentage로 바꾸니 안 죽습니다. 정확히 그 문제였습니다. 감사합니다!
Ctrl+Enter로 등록