IT/Java

java.lang.OutOfMemoryError: Java heap space 해결 순서: 원인 구분·힙 크기 설정·힙 덤프 수집 (JDK 26

바른정보블로그 2026. 9. 24. 18:11
자바 힙 메모리가 가득 차 넘치는 모습과 힙 덤프 분석을 표현한 대표 이미지

자바 애플리케이션이 돌다가 갑자기 java.lang.OutOfMemoryError: Java heap space를 뱉고 죽으면, 대부분 사람들은 곧바로 -Xmx부터 올립니다. 하지만 오라클 공식 트러블슈팅 가이드는 이 오류를 "메모리 누수의 신호일 수도 있고 단순한 설정 문제일 수도 있다"고 명확히 구분합니다. 원인을 가르지 않고 힙만 키우면 장애가 며칠 뒤로 미뤄질 뿐입니다. 이 글은 JDK 26 공식 문서를 기준으로 오류 메시지 읽는 법부터 힙 크기 설정, 힙 덤프 수집까지 순서대로 정리했습니다. (2026년 9월 24일 문서 확인)

빠른 결론 3줄

  • 먼저 OutOfMemoryError 뒤의 상세 메시지를 읽어 Java 힙 부족인지 네이티브·메타스페이스 부족인지 가릅니다.
  • 재현 환경에 -XX:+HeapDumpOnOutOfMemoryError를 붙여 두면 터지는 순간 힙 덤프가 자동 저장됩니다.
  • 풀 GC 이후에도 사용량이 계속 우상향하면 설정 문제가 아니라 누수입니다. -Xmx만 올리지 마세요.

상세 메시지별 원인 구분표

상세 메시지의미공식 문서가 권하는 조치
Java heap space객체를 자바 힙에 할당하지 못함힙 크기 확인·증가, 누수 여부 확인
GC overhead limit exceededGC에 시간의 약 98% 이상을 쓰면서 힙의 2% 미만만 회수하는 상태가 연속 5회힙 크기 증가(플래그로 끌 수는 있으나 근본 해결 아님)
Requested array size exceeds VM limitVM 구현 한계보다 큰 배열을 요청배열 크기 자체를 줄이도록 코드 수정
Metaspace클래스 메타데이터용 네이티브 메모리 고갈MaxMetaspaceSize 상향 또는 힙을 줄여 여유 확보
request size bytes for reason. Out of swap space?네이티브 힙 할당 실패, OS 메모리·스왑 부족OS 도구로 진단, 치명적 오류 로그 확인

즉 같은 예외라도 Java heap spaceMetaspace는 손대야 할 옵션이 완전히 다릅니다. 로그의 한 줄을 끝까지 읽는 것이 첫 단계입니다.

확인 전 체크리스트

  • 예외 메시지 전체와 스택 트레이스를 확보했는가 (OOM 발생 시 스택 트레이스가 출력됩니다)
  • 현재 실행에 -Xms/-Xmx가 지정돼 있는가, 아니면 기본값인가
  • 컨테이너·서버의 물리 메모리와 비교해 힙이 과도하게 잡혀 있지는 않은가
  • GC 로그 또는 모니터링 지표가 남아 있는가
  • 같은 증상이 특정 배치·업로드 같은 특정 작업에서만 나는가

단계별 해결 순서

  1. 기본 힙 크기를 파악합니다. 오라클 GC 튜닝 가이드에 따르면 JVM은 최대 힙을 물리 메모리의 1/4로 잡는 것이 기본값입니다. 또 프로세서 2개 이상, 물리 메모리 1792MB 이상이면 서버급 머신으로 간주해 G1 GC를 기본 선택합니다.
  2. 힙 크기를 명시합니다. java -Xms512m -Xmx2g -jar app.jar처럼 지정합니다. -Xmx는 1024의 배수이면서 2MB보다 커야 하고, -XX:MaxHeapSize와 같은 의미입니다. 서버 배포에서는 -Xms-Xmx를 같은 값으로 두는 경우가 많습니다.
  3. 힙 덤프를 자동 수집하도록 켭니다. -XX:+HeapDumpOnOutOfMemoryError를 붙이면 자바 힙 고갈로 OOM이 났을 때 힙이 파일로 덤프됩니다. 기본값은 꺼져 있습니다. 저장 위치는 -XX:HeapDumpPath=/경로/dump.hprof로 지정하며, 지정하지 않으면 현재 작업 디렉터리에 java_pid<pid>.hprof로 생성됩니다.
  4. 돌고 있는 프로세스에서 즉시 덤프를 뜹니다. jcmd <pid 또는 메인클래스> GC.heap_dump filename=heapdump.dmp를 사용합니다.
  5. 클래스별 사용량을 빠르게 훑습니다. jcmd <pid> GC.class_histogram filename=Myheaphistogram으로 히스토그램을 뽑습니다. 오라클 문서는 성능 부하가 적고 진단 기능이 강화된 jcmd 사용을 권장하며, 예전 jmap -histo pid도 같은 목적입니다. 2분 간격 등으로 여러 번 떠서 증가 추세를 보면 범인을 좁힐 수 있습니다.
  6. 누수인지 판정합니다. 문서는 풀 GC 이후 남아 있는 라이브 셋을 관찰하라고 안내합니다. 애플리케이션이 안정 상태이고 부하도 일정한데 라이브 셋이 시간이 지날수록 증가하면 누수의 강한 신호입니다. JConsole이나 JDK Mission Control로 힙·Old 영역 추이를 봅니다.
  7. 메타스페이스 문제라면 클래스 로더를 봅니다. jcmd <pid> VM.classloader_stats로 클래스 로더별 로드된 클래스 수를 확인합니다. 배포를 반복할수록 늘어난다면 클래스 로더 누수입니다.
  8. 덤프를 분석해 참조 경로를 찾습니다. 힙 덤프에서 가장 많은 메모리를 잡은 객체와 그 객체를 붙잡고 있는 참조 체인을 추적합니다. 캐시 맵, 정적 컬렉션, 리스너 미해제가 흔한 원인입니다.

주의사항

  • -XX:+HeapDumpOnOutOfMemoryError자바 힙 고갈로 인한 OOM에만 적용됩니다. 코드에서 직접 던진 OutOfMemoryError나 네이티브 스레드 생성 실패 같은 다른 자원 고갈에는 덤프가 생기지 않습니다.
  • 힙 덤프 파일은 힙 크기만큼 커질 수 있습니다. 운영 서버라면 디스크 여유와 저장 경로를 미리 확인하세요.
  • 덤프에는 실제 데이터가 그대로 담깁니다. 개인정보·토큰이 포함될 수 있으니 반출·공유에 주의해야 합니다.
  • -XX:-UseGCOverheadLimit로 GC overhead 오류를 끄는 것은 증상만 가리는 조치입니다.
  • finalize를 많이 쓰는 코드는 객체가 파이널라이제이션 큐에 쌓이면서 힙을 채울 수 있습니다. 오라클 문서가 직접 언급하는 시나리오입니다.
  • 물리 메모리를 넘는 -Xmx는 스와핑을 유발해 오히려 더 느려집니다.

그래도 해결되지 않을 때

  • GC 로그를 남겨 풀 GC 전후 사용량을 비교합니다. 로그에서도 라이브 셋 추이를 뽑을 수 있습니다.
  • Flight Recorder에 힙 통계를 켜서 시간에 따라 증가하는 객체 유형을 확인합니다.
  • 네이티브 힙 고갈이 의심되면 pmap, PerfMon 같은 OS 도구 출력을 주기적으로 모아 비교합니다.
  • 특정 요청에서만 터진다면 그 경로의 조회 건수·페이징 부재·대용량 파일 일괄 로딩을 먼저 의심합니다.

자주 묻는 질문

Q1. 힙을 두 배로 늘렸는데 며칠 뒤 또 터집니다.
A. 전형적인 누수 패턴입니다. 풀 GC 이후 라이브 셋이 계속 증가하는지 먼저 확인하세요. 증가한다면 힙 증설은 시간만 벌어 줄 뿐입니다.

Q2. 운영 서버에서 힙 덤프를 떠도 괜찮나요?
A. 덤프 중에는 애플리케이션이 멈추고 파일도 큽니다. 가능하면 트래픽이 적은 시간대에, 디스크 여유를 확보하고 진행하세요. 상시 대비용으로는 -XX:+HeapDumpOnOutOfMemoryError를 미리 걸어 두는 쪽이 부담이 적습니다.

Q3. IDE가 느린 것도 같은 문제인가요?
A. 다릅니다. IDE 자체의 힙, 빌드 도구의 JVM 힙, 실행되는 애플리케이션의 힙은 각각 별도 설정입니다. 어디에 옵션을 넣어야 하는지부터 구분해야 합니다.

함께 보면 좋은 글

공식 출처

이 글의 옵션과 수치는 JDK 26 공식 문서를 2026년 9월 24일에 확인한 내용입니다. JDK 버전에 따라 기본값과 지원 옵션이 다를 수 있으니, 사용 중인 버전의 문서를 함께 확인하세요.