Java Spring Boot 3.x 메모리 누수(Memory Leak) 진단: Heap Dump 분석과 Eclipse MAT 활용법

🎯 타겟 독자: OutOfMemoryError(OOM)로 서버가 주기적으로 셧다운 되는 현상을 겪는 백엔드 개발자, 힙 덤프(Heap Dump) 분석이 처음인 엔지니어.


📝 요약: Spring Boot 애플리케이션에서 메모리 누수가 발생했을 때, 단순히 힙 메모리(Xmx)를 늘리는 것은 임시방편입니다. 이 글에서는 VisualVMEclipse MAT를 사용하여 실행 중인 JVM의 덤프를 뜨고, 메모리를 점유하고 있는 범인(Dominator Tree)을 색출하여 근본적으로 해결하는 과정을 다룹니다.

메모리 분석 도구와 메모리 누수 의심

1. 문제 상황: "서버가 밤마다 죽어요"

트래픽이 적은 시간대임에도 불구하고 Java 애플리케이션이 java.lang.OutOfMemoryError: Java heap space 에러를 뱉으며 비정상 종료된다면, 이는 전형적인 메모리 누수(Memory Leak)의 신호입니다.

가비지 컬렉터(GC)가 더 이상 참조되지 않는 객체를 청소해야 하는데, 코드 어딘가에서 불필요한 객체를 계속 붙잡고(Reference) 있어서 GC가 수거하지 못하는 상황입니다. 이는 물통에 구멍이 난 것이 아니라, 물이 빠져나갈 배수구가 막힌 상황과 같습니다.

2. 진단 도구 준비

로그만 봐서는 어떤 객체가 메모리를 차지하는지 알 수 없습니다. 다음 두 가지 도구가 필요합니다.

  • VisualVM: JDK에 포함된 무료 도구로, 실시간 CPU/메모리 사용량을 모니터링하고 덤프 파일을 생성할 수 있습니다.
  • Eclipse MAT (Memory Analyzer Tool): 생성된 덤프 파일(.hprof)을 분석하여 보기 좋게 시각화해 주는 강력한 분석 툴입니다.

3. 분석 단계 (Step-by-Step)

Step 1. 힙 덤프(Heap Dump) 생성

서버가 죽기 직전의 상태를 스냅샷으로 남겨야 합니다. 운영 중인 서버라면 OOM 발생 시 자동으로 덤프를 뜨도록 시작 옵션을 추가하는 것이 좋습니다.

# JVM 시작 옵션에 추가 (OOM 발생 시 자동 저장) java -jar -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/log/dumps/ \ my-app.jar

Step 2. Eclipse MAT로 'Dominator Tree' 분석

생성된 .hprof 파일을 MAT로 열어 'Dominator Tree' 기능을 실행합니다. 이 기능은 가장 많은 메모리를 점유하고 있는 객체를 상위 순서대로 보여줍니다.

대부분의 경우, 상위 1~3개 객체가 전체 힙 메모리의 50% 이상을 차지하고 있다면 그곳이 범인입니다. 특히 Retained Heap(해당 객체가 사라지면 확보될 메모리 양)이 비정상적으로 큰 객체를 찾으세요.

4. 흔한 원인 Top 3 및 해결책

원인 유형 설명 해결 방법
Static Collection static List/Map에 데이터를 계속 담고 삭제하지 않음 static 사용 자제, LRU 캐시 사용
Unclosed Stream DB Connection, IO Stream을 close() 안 함 try-with-resources 구문 사용
Thread Local 요청 처리 후 스레드 변수 초기화 안 함 (Tomcat) Interceptor의 afterCompletion에서 제거

🚨 잘못된 코드 예시 (Static 사용)

public class MemoryLeaker { // static 리스트는 GC 대상이 되지 않음 (앱 종료 시까지 살아있음) // 데이터가 무한히 쌓여 결국 OOM 발생 private static final List<Object> cache = new ArrayList<>(); public void addToCache(Object data) { cache.add(data); } }

5. 결론

메모리 누수는 '천천히 다가오는 재앙'입니다. 배포 초기에는 문제가 없다가 데이터가 쌓이는 일주일 뒤에 터질 수 있습니다.

Spring Boot 개발 시에는 static 변수 사용을 극도로 자제하고, 외부 리소스를 사용할 때는 반드시 자원을 반납하는 습관을 들여야 합니다. OOM 발생 시 당황하지 말고 힙 덤프를 확보하는 것이 문제 해결의 90%입니다.

이 블로그의 인기 게시물

Docker 컨테이너 'Connection Refused' (Errno 111) 오류 해결 가이드

Redis 캐싱 전략 완벽 가이드: Look Aside부터 Write Back까지 (DB 부하 줄이기)

브라우저 렌더링 원리: Reflow와 Repaint 최적화 가이드 (CRP 심층 분석)