Java

OutOfMemory: IntelliJ IDEA로 Heap Dump(.hprof) 분석하기

누구세연 2025. 5. 28. 22:10

주니어 개발자라면 한 번쯤 마주할 수 있는(?) OutOfMemoryError (OOM)
저도 JVM 기반 백엔드 서비스를 운영하다가 실제로 OOM을 겪게 되었고, 이를 계기로 `.hprof` 파일을 분석해 보게 되었습니다.
(참고로 이번 OOM은 메모리 사용량 자체보다는 설정 이슈였지만, 메모리 문제에 대비한 분석 경험으로 의미 있었기에 글로 정리합니다!)

 

 

📦 .hprof 열기 (IntelliJ Ultimate 기준)

.hprof 파일은 JVM에서 OOM 발생 시 생성되는 힙 메모리 스냅샷입니다.
IntelliJ Ultimate에서는 아래와 같이 쉽게 열 수 있어요!

 

1. .hprof 파일을 IntelliJ로 드래그하거나 File → Open으로 열어줍니다.

 

2. 파일을 열어주면 자동으로 Heap Dump 분석 뷰가 열립니다.

이 뷰를 통해 메모리 사용 현황을 확인하고 문제의 원인을 추적할 수 있습니다.

 

 

🔍 주요 지표 해석하기

다양한 분석 탭들이 상단에 표시됩니다.

각 탭은 힙 메모리 상태를 다양한 관점에서 보여주며, 주요한 문제를 빠르게 파악하는 데 도움을 줍니다.

탭 이름 설명
Class 클래스별 객체 수와 메모리 점유량을 확인할 수 있습니다. 가장 자주 보는 탭이며, 어떤 객체가 가장 많은 메모리를 차지하는지 파악할 수 있어요!
Biggest Objects 가장 큰 단일 객체들을 보여줍니다. 큰 이미지, JSON, 파일 버퍼 등 단일 인스턴스가 문제일 수 있을 때 확인합니다.
GC Roots GC가 객체를 수거하지 못하게 만드는 참조 체인을 보여줍니다. 메모리 누수 의심 시 필수로 보는 탭이에요.
Merged Paths 여러 객체가 같은 GC Root에서 출발할 경우, 경로를 하나로 병합해서 보여줍니다. 참조 경로를 더 쉽게 파악할 수 있어요.
Summary 전체 힙 덤프의 요약 정보를 보여줍니다. 클래스 수, 인스턴스 수, 총 메모리 등을 한눈에 볼 수 있습니다.
Packages 패키지 단위로 메모리 사용량을 확인할 수 있습니다. 특정 라이브러리나 도메인에서 문제 있는지 분석할 때 유용해요.

 

 

 

📊 정렬 기준은 이렇게 보면 좋아요!

Heap Dump를 분석할 때, 보통 다음 두 가지 기준으로 정렬해서 많이 봅니다:

✅ Retained Size 기준 정렬

  • 해당 객체와 그 객체가 참조 중인 모든 객체의 메모리 합계
  • GC 되지 않는 객체군을 찾는 데 매우 효과적

✅ Count 기준 정렬

  • 같은 클래스의 인스턴스가 얼마나 많은지
  • 지나치게 많은 인스턴스가 있는 클래스는 캐시/컬렉션 누락 가능성 있음.

 

GC Root 확인: 왜 GC되지 않았을까?

메모리 누수의 원인을 파악하려면 GC Root로부터의 참조 체인을 분석하는 것이 매우 중요합니다.

예를 들어 아래 예시에서는 `StatefulPersistenceContext` 객체가 GC Root로 잡혀 있고,
이로 인해 Hibernate 세션이 닫히지 않아 업무에 필요로하는 인스턴스들이 계속 힙에 남아 있는 상황으로 추정됩니다.

이런 상황은 JPA나 Hibernate 사용 시 세션이 명확히 close되지 않은 경우 자주 발생합니다.

 

❓ 꼭 메모리 누수일까? 이번 경우는 아니었습니다

Heap Dump를 Retained 기준으로 정렬해보면 다음과 같은 객체들이 눈에 띕니다:

  • byte[], Object[] → 데이터 버퍼로서의 일반적인 사용 가능성
  • 저희 도메인에서 사용하는 정상적인 업무 객체
  • Hibernate 관련 객체들 → Hibernate의 영속성 컨텍스트에서 관리되는 중

👀 이 객체들이 실제로 명확한 GC Root를 통해 계속 참조되고 있다는 점은 확인되지만,
특정 객체가 비정상적으로 많거나, 의도치 않게 남아 있는 상태는 아니었습니다.

  • 메모리를 가장 많이 점유한 객체들은 대부분 업무 로직에 필요한 데이터 구조들이었습니다.
  • GC Root를 추적해도 순환 참조나 의심스러운 캐시/컬렉션이 발견되진 않았습니다.
  • 시스템 설정 또는 일시적인 처리량 급증에 따른 메모리 한계 초과 가능성이 높다고 판단됩니다.

즉, 이번 OOM은 "버그성 메모리 누수"보다는, 메모리를 정상적으로 사용 중인 객체가 많아졌거나,
GC가 돌기 전에 한계치에 도달한 상황에 가까웠습니다.

 

 

 

이번 분석을 통해 OOM이 항상 메모리 누수 때문은 아니라는 점을 다시 한 번 느꼈습니다. 코드 작성 시 메모리 구조나 객체 수명 주기에 더 신경 써야겠다는 생각도 들었어요…!

.hprof 파일을 열어 객체 수, Retained Size, GC Root 경로 등을 확인해 보면, 문제의 원인이 잘못된 코드나 누수인지, 혹은 단순한 설정/용량 이슈인지 구분할 수 있습니다.