이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • Memory Leak과 OOM 원인에서 붙잡아야 할 핵심 판단 기준은 무엇인가?
  • 회수되지 않는 참조와 limit 초과를 나눈다라는 기준은 어떤 JVM/OS 신호로 확인되는가?
  • Java/Spring 코드에서 Memory Leak과 OOM 원인 판단 기준은 어떤 장애 신호로 드러나는가?

개요

이 문서의 주제는 Memory Leak과 OOM 원인이다. Java 서버에서 memory leak과 다양한 OOM 원인을 구분하는 방법을 정리한다.

먼저 붙잡을 기준은 다음 문장이다. 회수되지 않는 참조와 limit 초과를 나눈다. 이 문서는 이 기준을 Java/Spring 실행 흐름, JVM/OS 관찰 지표, 장애 판단 순서로 연결한다.

핵심 모델

  • 판단 기준: leak은 사용량이 아니라 회복되지 않는 baseline 증가다.
  • Memory leak은 사용 후 참조가 끊기지 않아 GC 후에도 객체가 남는 패턴이다.
  • OOM 원인은 heap, metaspace, direct buffer, native allocation, OS kill로 나뉜다.
  • ThreadLocal, cache, listener, scheduler, static collection은 leak의 흔한 시작점이다.
  • OOM 후 재시작은 완화일 수 있지만 leak 원인 증거를 지울 수 있다.

문서별 핵심 구분

  • heap leak은 GC 후 바닥선이 계속 올라가는 패턴으로 의심한다.
  • OOM 메시지 종류에 따라 heap, metaspace, direct, OS kill을 나눈다.
  • cache, ThreadLocal, static collection은 흔한 누수 출발점이다.

원리

Memory leak은 사용이 끝난 객체나 native 자원을 계속 참조해 회수 대상이 되지 않는 상태다. Java에서는 GC가 있어도 static collection, cache, ThreadLocal, listener, scheduler task가 참조를 붙잡으면 leak이 된다.

OOM은 하나의 원인이 아니다. Java heap space, GC overhead limit exceeded, Metaspace, Direct buffer memory, unable to create native thread, OS OOM kill은 각각 보는 지표와 대응이 다르다.

Leak은 순간 사용량보다 baseline 상승으로 본다. 트래픽이 내려간 뒤에도 GC 후 heap used가 이전보다 높아지고, class histogram에서 특정 객체 수가 계속 늘면 누수를 의심한다.

Memory Leak과 OOM 원인 관점에서 중요한 점은 “회수되지 않는 참조”와 “limit 초과”를 나누는 것이다. heap 누수는 코드 참조를 끊어야 하고, direct/native/container limit 문제는 heap dump만으로 원인이 보이지 않을 수 있다.

Java/Spring 연결

  • Spring singleton bean의 map/cache는 요청 수와 함께 커지면 heap leak 후보가 된다.
  • ThreadLocal, MDC, SecurityContext 정리 누락은 thread pool에서 오래 남을 수 있다.
  • WebClient buffer, multipart, file upload는 heap 밖 memory leak처럼 보일 수 있다.
  • OutOfMemoryError 메시지를 분류해야 heap dump가 필요한지 native 분석이 필요한지 결정할 수 있다.
  • 재시작 전 class histogram, GC log, heap dump 가능성을 먼저 검토한다.

코드와 설정 예시

List<byte[]> retained = new ArrayList<>();
 
public void cache(byte[] payload) {
    retained.add(payload); // 종료 조건 없는 보관은 누수 후보가 된다.
}

예시는 종료 조건 없는 보관이 heap leak으로 이어지는 가장 단순한 형태다. 실제 서비스에서는 사용자별 cache, export 결과 보관, ThreadLocal 값, 이벤트 listener 등록 누락이 같은 구조를 만든다.

관찰 명령

jcmd <pid> GC.class_histogram | head
jcmd <pid> GC.heap_info
jcmd <pid> Thread.print | grep ThreadLocal -n

Memory Leak과 OOM 원인 관찰 명령은 결론이 아니라 가설 확인 도구다. 운영 중에는 반복 간격, 대상 PID, 수집 시각을 함께 남기고 출력이 큰 명령은 범위를 제한한다.

지표 해석

신호먼저 의심할 것다음 확인
GC 후 heap used가 계속 상승heap leak 또는 cache 증가class histogram, heap dump
heap은 안정적인데 RSS 상승native/direct memory, thread stackNMT, /proc/<pid>/status
Full GC와 pause 증가allocation pressure 또는 old gen 압박GC log, allocation hot spot
Swap in/out 발생OS memory pressurevmstat, container limit, OOM kill log

Memory Leak과 OOM 원인에서는 핵심 신호와 보조 지표를 같은 시간축에서 맞춘다. 먼저 변한 신호와 뒤따라 포화된 신호를 분리해야 원인과 결과를 구분할 수 있다.

장애 상황에서 보는 순서

  1. OOM 메시지와 process 종료 원인을 먼저 분류한다.
  2. GC 후 heap baseline이 요청 처리 후 내려오는지 본다.
  3. RSS만 증가하는지 heap도 같이 증가하는지 분리한다.
  4. class histogram으로 증가한 객체 타입을 먼저 확인한다.
  5. 최근 cache, batch, listener, buffering 변경을 코드에서 추적한다.

이 순서는 OOM을 heap 부족 하나로 단정하지 않기 위한 루틴이다.

실전 팁

  • OOM 메시지의 정확한 종류를 먼저 기록한다.
  • GC 후 heap used baseline이 트래픽 감소 뒤에도 내려오는지 본다.
  • class histogram은 한 번보다 시간 간격을 두고 비교한다.
  • ThreadLocal, cache, static collection, listener, scheduler를 코드 리뷰 후보로 둔다.
  • OOM 후 자동 재시작이 있어도 dump, histogram, GC log 보존 경로를 먼저 확인한다.

주니어 팁

  • Memory Leak과 OOM 원인 주제는 용어 정의보다 장애 때 어떤 증거로 확인할지까지 연결해 익힌다.
  • 정상 상태에서 이 문서의 관찰 명령을 실행해 Memory Leak과 OOM 원인 기준선을 남긴다.
  • 장애 시에는 Memory Leak과 OOM 원인 신호가 평소 기준선에서 벗어난 시각과 방향을 먼저 본다.

시니어 팁

  • Memory Leak과 OOM 원인 주제의 영향이 pool, queue, retry, timeout으로 번지는 경로를 설계 리뷰에 포함한다.
  • Memory Leak과 OOM 원인 runbook에는 실행 명령, 정상 범위, 해석 기준, 금지할 대응을 같이 둔다.
  • Memory Leak과 OOM 원인 판단이 container, LB, 다중 인스턴스 환경에서도 같은 의미인지 확인한다.

Guru급 팁

  • Memory Leak과 OOM 원인 주제는 단일 snapshot보다 변화 방향이 중요하다. 같은 명령을 간격을 두고 실행해 상태가 증가, 유지, 회복 중 어디인지 본다.
  • 고급 도구는 Memory Leak과 OOM 원인 가설을 좁힐 때만 사용하고 기본 지표와 모순되는지 먼저 확인한다.
  • Memory Leak과 OOM 원인 조치 후에는 CPU, memory, socket, disk, pool 중 다음 포화 지점이 어디로 이동했는지 확인한다.

위험 신호!

  • OutOfMemoryError를 모두 heap 부족으로 처리한다.
  • heap dump 없이 heap size만 계속 키운다.
  • GC가 자주 돈다는 사실만 보고 leak이라고 단정한다.
  • cache eviction, ThreadLocal remove, listener unregister 정책이 없다.
  • OS OOM kill인데 JVM exception 로그만 찾는다.

확인 질문

확인 질문

  • Memory Leak과 OOM 원인 주제를 장애 중 확인할 때 Application 로그만으로 부족한 이유는 무엇인가?
    • 로그는 코드 경로를 보여 주지만 Memory Leak과 OOM 원인의 JVM/OS 자원 상태, 대기 위치, 포화 방향은 별도 지표로 확인해야 하기 때문이다.
  • Memory Leak과 OOM 원인 관련 설정이나 pool 크기를 바꾸기 전에 확인할 것은 무엇인가?
    • 먼저 변한 신호, 하위 자원 capacity, 변경이 완화인지 증폭인지 판단할 기준을 Memory Leak과 OOM 원인 지표로 확인해야 한다.
  • Memory Leak과 OOM 원인 재발을 막기 위해 장애 기록에 무엇을 남겨야 하는가?
    • 발생 시간, 영향 범위, 사용한 명령, Memory Leak과 OOM 원인 핵심 지표, 원인 판단, 완화 조치, 근본 수정, 남은 리스크를 남긴다.

참고 문서