메모리 누수(Memory Leak) 진단 & 해결 — JVM, Node.js, Python
메모리 누수(Memory Leak)는 조용히 쌓이다가 OOM(Out of Memory) 으로 갑자기 터집니다. Soak Test에서 시간이 지날수록 응답 시간이 증가한다면 누수를 의심하세요.
메모리 누수의 3가지 패턴
섹션 제목: “메모리 누수의 3가지 패턴”정상: 메모리 ████░░░░░░ GC 후 복구성장형 누수: ████████████░░ GC 후 일부 복구, 전반적 증가고착형 누수: ████████████████ 복구 없음 → OOM 불가피공통 원인:
- 해제되지 않는 이벤트 리스너
- 무한히 증가하는 캐시 (만료 없는 Map, 딕셔너리)
- DB 커넥션 / 파일 핸들 미반납
- 글로벌 변수에 쌓이는 데이터
JVM 메모리 누수 진단
섹션 제목: “JVM 메모리 누수 진단”1단계: GC 로그로 패턴 파악
섹션 제목: “1단계: GC 로그로 패턴 파악”# JVM 시작 옵션에 추가java -Xms512m -Xmx1g \ -XX:+PrintGCDetails \ -XX:+PrintGCDateStamps \ -Xloggc:/var/log/app/gc.log \ -jar app.jar# GCEasy (온라인 분석) 또는 GCViewer로 시각화# GC 후 힙이 완전히 복구되지 않는 패턴 → 누수 의심2단계: 힙 덤프 분석
섹션 제목: “2단계: 힙 덤프 분석”# 실행 중인 JVM 힙 덤프jmap -dump:format=b,file=heap.hprof <PID>
# OOM 발생 시 자동 덤프 (시작 옵션에 추가)-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/var/log/app/# Eclipse MAT (Memory Analyzer Tool)로 분석# → "Leak Suspects" 리포트 자동 생성# → 가장 많은 메모리를 점유하는 객체 트리 확인3단계: async-profiler로 할당 추적
섹션 제목: “3단계: async-profiler로 할당 추적”# 어느 코드에서 객체가 많이 생성되는지 추적./profiler.sh -e alloc -d 30 -f alloc.html <PID>Node.js 메모리 누수 진단
섹션 제목: “Node.js 메모리 누수 진단”일반적인 누수 패턴
섹션 제목: “일반적인 누수 패턴”// 누수 1: 이벤트 리스너 미제거class DataProcessor extends EventEmitter { process() { // 매 호출마다 리스너가 쌓임 db.on('data', (row) => this.handle(row)); // ❌
// 올바른 방법: 한 번만 등록하거나 명시적 제거 const handler = (row) => this.handle(row); db.on('data', handler); db.once('end', () => db.removeListener('data', handler)); // ✅ }}
// 누수 2: 만료 없는 캐시const cache = new Map();function getCached(key) { if (!cache.has(key)) { cache.set(key, fetchData(key)); // 영원히 쌓임 ❌ } return cache.get(key);}
// 해결: LRU 캐시 사용const LRU = require('lru-cache');const cache = new LRU({ max: 1000, ttl: 1000 * 60 * 5 }); // 최대 1000개, 5분 TTL ✅Chrome DevTools로 힙 스냅샷 비교
섹션 제목: “Chrome DevTools로 힙 스냅샷 비교”# Node.js 인스펙터 활성화node --inspect app.js
# Chrome: chrome://inspect → 대상 연결# Memory 탭 → "Take heap snapshot"# 일정 시간 후 다시 스냅샷# "Comparison" 뷰에서 증가한 객체 확인clinic.js를 활용한 자동 진단
섹션 제목: “clinic.js를 활용한 자동 진단”npm install -g clinic
# 메모리 프로파일링clinic heapprofile -- node app.js# 결과 HTML 자동 생성: 함수별 메모리 할당 시각화Python 메모리 누수 진단
섹션 제목: “Python 메모리 누수 진단”# tracemalloc으로 메모리 할당 추적import tracemalloc
tracemalloc.start()
# ... 코드 실행 ...
snapshot = tracemalloc.take_snapshot()top_stats = snapshot.statistics('lineno')
print("=== 메모리 상위 10개 할당 지점 ===")for stat in top_stats[:10]: print(stat)# memory_profiler로 함수별 메모리 사용량from memory_profiler import profile
@profiledef process_large_dataset(): data = load_all_records() # 메모리 급증 지점 확인 results = [transform(r) for r in data] return resultsSoak Test로 누수 검증
섹션 제목: “Soak Test로 누수 검증”검증 방법:1. 초기 메모리 사용량 기록2. 50 VU로 4~8시간 Soak Test 실행3. 시간별 메모리 사용량 그래프 확인
판단 기준:✅ 정상: GC 후 초기 메모리 수준 유지⚠️ 의심: 완만하게 증가하다 일정 수준 이상에서 안정❌ 누수: 지속적으로 증가, 복구 없음# Prometheus로 JVM 힙 사용량 추이jvm_memory_used_bytes{area="heap"}
# Node.js process 메모리process_resident_memory_bytes다음 단계
섹션 제목: “다음 단계”이 가이드를 내 서비스에 직접 적용해 보세요.
TestForge 무료 스캔 시작 →