Programming/Java

OutOfMemoryError 발생 상황과 해결 방법

마실개 2025. 4. 18. 16:24
반응형

1. Java heap space

발생 징후

  • java.lang.OutOfMemoryError: Java heap space
  • GC 로그에 Full GC 반복 + 힙 회수량 저조

해결 절차

1단계. GC 로그 확인

java -Xlog:gc* -Xmx2g -Xms2g -jar app.jar
  • Full GC가 자주 발생하고 회수량이 적다면 힙 사이즈 부족 가능성

2단계. 힙 덤프 생성

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof
  • 애플리케이션 실행 시 옵션으로 추가하여 OOM 발생 시 메모리 상태를 기록

3단계. 힙 분석 도구 사용 (예: Eclipse MAT)

  • 덤프 파일(.hprof)을 Eclipse MAT에서 열어 Dominator Tree 분석
  • 객체 점유율이 높은 클래스나 컬렉션 파악

4단계. 코드 점검 및 개선

  • 캐시 구조: ConcurrentHashMap, Guava Cache 등을 사용할 경우 최대 크기 설정
  • 루프 내 객체 반복 생성이 있는지 점검
  • 필요시 힙 사이즈 증가 (JVM 실행 시 아래 옵션 추가)
-Xmx4g -Xms4g

2. GC overhead limit exceeded

발생 징후

  • java.lang.OutOfMemoryError: GC overhead limit exceeded

해결 절차

1단계. GC 로그 분석

  • Full GC가 1초 간격으로 발생하며 회수량이 매우 적음

2단계. 코드 점검

  • 참조가 끊기지 않는 컬렉션이나 객체 보유 상태 확인

3단계. 임시 대응 (JVM 옵션)

-XX:-UseGCOverheadLimit
  • 해당 옵션은 GC 시간 제한 조건을 제거하지만, 근본적인 해결은 아님

4단계. GC 변경 고려

  • GC 전략을 변경하여 회수 효율 개선
-XX:+UseG1GC
  • 애플리케이션 실행 시 위 옵션 추가

3. Metaspace (JDK 8 이상)

발생 징후

  • java.lang.OutOfMemoryError: Metaspace

해결 절차

1단계. 힙 덤프 및 클래스 수 분석

  • ClassLoader 누수 의심 시 Dominator Tree 분석

2단계. 원인 추정

  • Spring Boot나 톰캣 같은 환경에서 Hot deploy 또는 리로딩 발생 여부 점검

3단계. 설정 변경

-XX:MaxMetaspaceSize=512m
  • JVM 실행 옵션에 추가하여 메타스페이스 크기 제한을 명시적으로 설정

4. Direct buffer memory

발생 징후

  • java.lang.OutOfMemoryError: Direct buffer memory

해결 절차

1단계. 메모리 크기 설정 확인

-XX:MaxDirectMemorySize=256m
  • 기본값은 -Xmx 값에 따라 자동 결정되며, 명시적으로 지정하는 것이 좋음

2단계. 사용 패턴 점검

  • Netty, Kafka 등에서 ByteBuffer 할당 후 해제를 하지 않거나, GC에 의존하는 구조인지 확인

3단계. 개선 방안

  • 버퍼 재사용 구조 도입 (예: Netty의 PooledByteBufAllocator)
  • 불필요한 allocateDirect 호출 지양

5. Unable to create new native thread

발생 징후

  • java.lang.OutOfMemoryError: unable to create new native thread

해결 절차

1단계. 시스템 제한 확인

ulimit -u
ps -eLf | grep java | wc -l
  • ulimit -u: 사용자당 생성 가능한 최대 스레드 수 확인

2단계. 코드 점검

  • Thread를 직접 생성하거나 newCachedThreadPool 사용으로 무제한 스레드가 생기는 구조인지 확인

3단계. 설정 및 개선

  • 스레드 풀 사용 시 최대 스레드 수 제한
Executors.newFixedThreadPool(100);
  • JVM 실행 시 스택 크기 조정 (스레드 1개당 메모리 소비 감소)
-Xss512k

  • 모든 OutOfMemoryError는 heap 문제가 아님
  • 오류 메시지를 정확히 분류하고 원인 분석이 최우선
  • HeapDump, GC 로그 분석 도구의 활용은 필수
  • Eclipse MAT, VisualVM 등 시각화 도구를 사용하여 빠르게 원인을 파악 가능

 

OutOfMemoryError가 발생했다고 해서 무조건 코드가 잘못됐다는 뜻은 아니다. 상황에 따라 크게 두 가지로 나눌 수 있다:

1. 코드의 문제로 인한 메모리 누수

예시:

  • 컬렉션에 객체를 계속 넣지만 제거하지 않음 (예: Map에 캐시하면서 크기 제한 없음)
  • 이벤트 리스너나 콜백을 등록하고 해제하지 않아 참조가 계속 남아 있음
  • 클래스 로더 누수 (웹 서버에서 redeploy 반복 시)

 

이런 경우는 명확한 버그 혹은 설계 미스로 인한 문제이다.

해당 객체가 GC 대상이 되지 못하니까 메모리를 차지한 채로 계속 남고, 결국 OOM으로 이어진다.

 

→ 해결: 힙 덤프 분석, 코드 리뷰, 참조 구조 개선


2. 실제 트래픽 또는 데이터 증가에 의한 메모리 부족

예시:

  • 원래는 하루 1만건 처리하던 시스템이 어느 날부터 100만건을 처리하게 됨
  • 사용자가 증가하면서 캐시, 큐, 버퍼 등에 실질적으로 더 많은 메모리가 필요해짐

 

이건 코드가 잘못됐다기보단, 설계 당시 예상하지 못한 부하나 데이터 증가에 따른 자연스러운 결과일 수 있다.

JVM의 기본 메모리 할당이 너무 작거나, GC 전략이 현 상황에 적합하지 않을 수 있다.

 

→ 해결: JVM 메모리 설정 조정 (-Xmx, -XX:MaxMetaspaceSize 등), GC 정책 변경, 스케일링


결론적으로

상황 코드 문제? 설정 조정으로 해결 가능?
참조 누수 / 메모리 해제 안됨 설정만으론 안 됨
실제 처리량 증가 아닐 수 있음 설정, 인프라 확장으로 가능
GC 오버헤드 혼합 전략 + 코드 점검 병행

반응형