Spring Boot 메모리 누수 진단과 해결법
Spring Boot 애플리케이션에서 발생하는 메모리 누수를 단계별로 진단·분석하고 heap dump 분석·GC 로그 확인·코드 수정 등 주요 도구와 방법을 사례 중심으로 정리
목차
개요
메모리 사용량이 점점 커지다가 결국 OOM(OutOfMemoryError)으로 종료되는 문제는 서비스 안정성에 큰 영향을 준다. 이 글에서는 spring boot 메모리 누수 찾기 를 위한 기초 개념과 실무 절차를 정리한다. 초보자도 따라올 수 있도록 진단 순서와 대표적인 원인, 해결법을 예제와 함께 설명한다.
증상 확인
먼저 증상을 정확히 확인한다. 메모리 누수는 다음과 같은 형태로 나타난다.
- 애플리케이션 메모리 사용량이 지속적으로 증가
- GC가 자주 발생하지만 메모리 회복이 미미
- 서버 부하 증가와 응답 지연
- 최종적으로 OutOfMemoryError 발생
운영 환경에서는 GC 로그와 프로세스 메모리 지표부터 수집한다.
진단 준비
진단을 위해 다음 도구를 준비한다.
- jcmd, jmap, jstat (JDK 기본 도구)
- VisualVM 또는 YourKit, JProfiler 같은 프로파일러
- Eclipse Memory Analyzer (MAT)로 heap dump 분석
진단 절차
1) 프로세스 확인 및 GC 로그 확보
먼저 프로세스 ID를 확인하고 GC 로그를 활성화한다. 운영 중이라면 jcmd로 간단하게 요청해 GC 정보를 얻는다.
jcmd <pid> GC.run
jstat -gc <pid> 1000 10
2) 힙 덤프 수집
OOM 발생 전후로 힙 덤프를 확보한다. jmap을 사용하거나 jcmd로 요청한다. 덤프 파일은 MAT에서 분석한다.
jmap -dump:live,format=b,file=heap.hprof <pid>
# 또는
jcmd <pid> GC.heap_dump heap.hprof
3) 힙 덤프 분석 (heap dump 분석 spring boot)
MAT를 열고 heap.hprof를 로드한다. 주요 분석 포인트는 다음과 같다.
- Dominator Tree로 큰 객체 그룹 찾기
- Top Consumers로 메모리 사용 큰 클래스 확인
- Leak Suspects 리포트로 잠재적 누수 경로 탐색
예를 들어 많은 메모리를 차지하는 객체가 static 컬렉션이나 ThreadLocal이면 누수 가능성이 높다.
대표적 원인과 해결법
1) Static 컬렉션에 누적
싱글톤 빈 또는 static 필드에 컬렉션을 두고 제거하지 않으면 메모리가 계속 쌓인다. 아래 코드는 문제가 되는 예와 수정 예이다.
// 문제 예시
public class LeakHolder {
private static List<Object> cache = new ArrayList<>();
public static void add(Object o) {
cache.add(o);
}
}
// 해결 예시
public class LeakHolderFixed {
private static final int MAX = 1000;
private static Deque<Object> cache = new ArrayDeque<>();
public static synchronized void add(Object o) {
cache.addLast(o);
if (cache.size() > MAX) {
cache.removeFirst();
}
}
}
2) ThreadLocal 미해제
웹 애플리케이션에서 ThreadLocal을 사용하면 스레드 풀의 스레드가 재사용될 때 의도치 않은 참조가 남을 수 있다. 작업이 끝난 후 반드시 remove()를 호출한다.
3) 캐시 설계 오류
캐시에 TTL이나 최대 크기 제한을 두지 않으면 메모리가 무한히 증가한다. Caffeine이나 Guava Cache 같은 검증된 라이브러리를 사용해 eviction 정책을 적용한다.
4) 리스너/콜백 미해제
등록한 리스너를 제거하지 않으면 참조가 남아 객체가 회수되지 않는다. 등록-해제 라이프사이클을 명확히 관리한다.
5) 외부 리소스 미반납
JDBC 커넥션, InputStream 같은 리소스가 닫히지 않으면 누수가 발생한다. try-with-resources 패턴을 사용하면 안전하다.
try (InputStream in = resource.openStream()) {
// 처리
}
실전 검증 방법
문제 수정 후에는 재현 테스트와 장기간 부하 테스트로 검증한다. 다음 절차를 권장한다.
- 수정 전/후 힙 덤프 비교
- GC 로그와 메트릭을 장시간 수집
- 프로파일러로 객체 생성 패턴 확인
유용한 명령 모음
# 프로세스 확인
jps -l
# 힙 덤프 생성
jmap -dump:live,format=b,file=heap.hprof <pid>
# GC 강제 실행
jcmd <pid> GC.run
# GC 상태 확인
jstat -gc <pid> 1000
정리
memory leak spring boot 해결 을 위해서는 증상 파악, 힙 덤프 확보, MAT 등의 도구로 분석, 코드 개선과 검증의 순서가 중요하다. 특히 spring boot 메모리 누수 찾기 에서는 운영 환경의 로그와 메트릭이 중요한 단서가 된다. 도구를 익히고 반복적으로 검증하면 안정성을 크게 개선할 수 있다.