Java heap dump로 메모리 누수 원인 찾는 흐름
java 환경에서 heap dump는 특정 시점의 메모리 상태를 그대로 덤프한 파일입니다. 이 파일을 분석하면 어떤 객체가 얼마나 메모리를 점유하고 있는지 확인할 수 있고, 메모리누수의 방향을 비교적 명확하게 잡을 수 있습니다.
문제가 되는 상황
메모리 누수는 보통 다음과 같은 패턴으로 드러납니다.
- GC 이후에도 사용량이 줄지 않음
- 시간이 지날수록 heap 사용량이 점진적으로 증가
- 특정 기능 실행 이후 메모리 회수가 되지 않음
이 단계에서는 단순 로그만으로는 원인을 특정하기 어렵기 때문에 heap dump를 떠서 실제 객체를 보는 것이 가장 빠릅니다.
heap dump 생성 방법
heap dump는 JVM에서 직접 생성하거나, OOM 발생 시 자동으로 생성하도록 설정할 수 있습니다.
OOM 시 자동 생성 설정
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dump
이 설정을 넣어두면 OutOfMemoryError 발생 시 자동으로 heap dump가 생성됩니다.
수동으로 생성하는 방법
jmap -dump:live,format=b,file=heap.hprof <pid>
live 옵션을 주면 현재 살아있는 객체 기준으로 덤프를 생성합니다.
heap dump 분석 도구 선택
heap dump는 hprof 파일 형태로 생성되며, 일반적으로 아래 도구를 사용해 분석합니다.
- Eclipse MAT
- VisualVM
- IntelliJ Profiler
이 중에서는 Eclipse MAT가 기능이 가장 풍부해서 많이 사용됩니다.
실제 분석 흐름
heap dump 분석은 정해진 정답이 있는 작업이라기보다, 의심되는 방향을 좁혀가는 과정에 가깝습니다.
1. Dominator Tree 확인
가장 먼저 보는 것은 Dominator Tree입니다. 이 뷰는 어떤 객체가 메모리를 많이 점유하고 있는지를 보여줍니다.
특히 retained size 기준으로 정렬하면 실제로 메모리를 많이 잡고 있는 객체를 빠르게 찾을 수 있습니다.
2. 비정상적으로 큰 컬렉션 찾기
실무에서는 List, Map 같은 컬렉션이 예상보다 커지는 경우가 많습니다.
- HashMap 크기가 비정상적으로 큼
- ArrayList가 계속 증가만 하고 줄지 않음
이 경우 대부분 객체가 제거되지 않고 계속 쌓이고 있는 상황입니다.
3. GC Root 참조 추적
객체가 왜 살아있는지 확인하려면 GC Root까지의 참조 경로를 봐야 합니다.
이 과정에서 static 변수나 싱글톤 객체가 의도치 않게 참조를 유지하고 있는 경우를 자주 발견합니다.
4. Leak Suspects Report 활용
MAT에서는 자동으로 Leak Suspects Report를 생성해줍니다.
이 리포트는 메모리를 많이 차지하는 객체와 의심 경로를 요약해주기 때문에 처음 분석할 때 기준점으로 사용하기 좋습니다.
실제 많이 나오는 메모리 누수 패턴
여러 번 분석해보면 비슷한 유형이 반복됩니다.
- 캐시 제거 로직 누락
- ThreadLocal 사용 후 remove 누락
- Listener 등록 후 해제 누락
- static 컬렉션에 계속 데이터 축적
이런 패턴은 코드상으로는 크게 문제가 없어 보이지만, 장시간 실행되면 누적되는 형태라 발견이 늦어지는 경우가 많습니다.
분석할 때 주의할 점
heap dump 분석은 몇 가지 주의할 부분이 있습니다.
- 한 시점만 보면 판단이 어렵다
- 정상적인 객체도 크게 보일 수 있다
- GC 타이밍에 따라 결과가 달라질 수 있다
가능하다면 시간 간격을 두고 여러 dump를 비교하는 방식이 더 정확합니다.
정리
heap dump 분석은 메모리누수를 해결할 때 가장 직접적인 방법입니다. 로그나 지표로 추정하는 단계에서 벗어나 실제 객체를 확인할 수 있기 때문입니다.
핵심은 큰 객체를 찾고, 왜 해제되지 않는지 참조 경로를 따라가는 것입니다. 이 흐름만 익숙해지면 대부분의 메모리 문제는 방향을 잡을 수 있습니다.
'개발 > JAVA' 카테고리의 다른 글
| [JAVA] CPU 100% 찍는 Java 프로세스 원인 찾는 방법 (0) | 2026.05.03 |
|---|---|
| [JAVA] Thread 수 증가로 인한 OutOfMemoryError 발생 원인 (0) | 2026.05.02 |
| [JAVA] JVM 옵션 잘못 건드렸다가 서비스 장애 경험 (0) | 2026.04.30 |
| [JAVA] G1GC로 바꿨는데 오히려 성능이 나빠진 이유 (0) | 2026.04.29 |
| [JAVA] Full GC 때문에 latency 튀는 문제 추적한 과정 (0) | 2026.04.28 |
